Transfers & Payouts
Automate payouts to bank accounts, manage recipients, and schedule recurring transfers using Omise's Transfers API for seamless fund distribution.
Overviewโ
Transfers allow you to send funds from your Omise account balance to bank accounts. This is useful for marketplace payouts, affiliate commissions, refunds to non-card payment methods, supplier payments, or any scenario where you need to distribute funds automatically.
Key Features:
- โ Automated payouts - API-driven or scheduled transfers
- โ Multiple recipients - Save and reuse recipient information
- โ Flexible scheduling - Daily, weekly, or monthly transfers
- โ Multi-currency - Support for multiple currencies
- โ Batch transfers - Process multiple payouts efficiently
- โ Webhook notifications - Real-time transfer status updates
- โ Bank verification - Automatic recipient bank validation
How It Worksโ
Transfer Lifecycle:
- pending - Transfer created, queued for processing
- sent - Transfer sent to bank
- paid - Recipient received funds
- failed - Transfer failed (insufficient balance, invalid account)
Implementationโ
Step 1: Create Recipientโ
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/recipients \
-u skey_test_YOUR_SECRET_KEY: \
-d "name=John Doe" \
-d "email=john@example.com" \
-d "type=individual" \
-d "bank_account[brand]=bbl" \
-d "bank_account[number]=1234567890" \
-d "bank_account[name]=John Doe"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const recipient = await omise.recipients.create({
name: 'John Doe',
email: 'john@example.com',
type: 'individual',
bank_account: {
brand: 'bbl', // Bangkok Bank
number: '1234567890',
name: 'John Doe'
},
metadata: {
user_id: '12345',
payout_type: 'commission'
}
});
console.log('Recipient ID:', recipient.id);
<?php
$recipient = OmiseRecipient::create(array(
'name' => 'John Doe',
'email' => 'john@example.com',
'type' => 'individual',
'bank_account' => array(
'brand' => 'bbl',
'number' => '1234567890',
'name' => 'John Doe'
)
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
recipient = omise.Recipient.create(
name='John Doe',
email='john@example.com',
type='individual',
bank_account={
'brand': 'bbl',
'number': '1234567890',
'name': 'John Doe'
}
)
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
recipient = Omise::Recipient.create({
name: 'John Doe',
email: 'john@example.com',
type: 'individual',
bank_account: {
brand: 'bbl',
number: '1234567890',
name: 'John Doe'
}
})
recipient, err := client.Recipients().Create(&operations.CreateRecipient{
Name: "John Doe",
Email: "john@example.com",
Type: "individual",
BankAccount: &BankAccount{
Brand: "bbl",
Number: "1234567890",
Name: "John Doe",
},
})
Recipient recipient = client.recipients().create(new Recipient.CreateParams()
.name("John Doe")
.email("john@example.com")
.type("individual")
.bankAccount(new BankAccount()
.brand("bbl")
.number("1234567890")
.name("John Doe")));
var recipient = await client.Recipients.Create(new CreateRecipientRequest
{
Name = "John Doe",
Email = "john@example.com",
Type = "individual",
BankAccount = new BankAccount
{
Brand = "bbl",
Number = "1234567890",
Name = "John Doe"
}
});
Supported Banks (Thailand):
bbl- Bangkok Bankkbank- Kasikorn Bankscb- Siam Commercial Bankktb- Krung Thai Bankbay- Bank of Ayudhya (Krungsri)tmb- TMB Bankciti- Citibankuob- United Overseas Bank- And more...
Step 2: Create Transferโ
const transfer = await omise.transfers.create({
amount: 100000, // THB 1,000.00
recipient: recipient.id,
metadata: {
order_id: '12345',
payout_date: new Date().toISOString()
}
});
console.log('Transfer ID:', transfer.id);
console.log('Status:', transfer.status); // 'pending'
Step 3: Check Transfer Statusโ
// Retrieve transfer
const transfer = await omise.transfers.retrieve('trsf_test_...');
console.log('Status:', transfer.status);
// 'pending' โ 'sent' โ 'paid' or 'failed'
if (transfer.status === 'paid') {
console.log('Transfer completed successfully');
console.log('Paid at:', transfer.paid_at);
} else if (transfer.status === 'failed') {
console.log('Transfer failed:', transfer.failure_message);
}
Step 4: Handle Webhooksโ
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
switch (event.key) {
case 'transfer.create':
handleTransferCreated(event.data);
break;
case 'transfer.send':
handleTransferSent(event.data);
break;
case 'transfer.pay':
handleTransferPaid(event.data);
break;
case 'transfer.fail':
handleTransferFailed(event.data);
break;
}
res.sendStatus(200);
});
async function handleTransferPaid(transfer) {
console.log(`Transfer ${transfer.id} completed`);
// Update database
await db.payouts.update({
transfer_id: transfer.id,
status: 'completed',
paid_at: transfer.paid_at
});
// Notify recipient
await sendPayoutConfirmation(transfer.recipient, transfer);
}
Complete Marketplace Payout Exampleโ
// Marketplace platform with seller payouts
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// Register seller for payouts
app.post('/sellers/:sellerId/register-payout', async (req, res) => {
try {
const { sellerId } = req.params;
const { bank_brand, account_number, account_name } = req.body;
const seller = await db.sellers.findOne({ id: sellerId });
// Create recipient in Omise
const recipient = await omise.recipients.create({
name: account_name,
email: seller.email,
type: 'individual',
bank_account: {
brand: bank_brand,
number: account_number,
name: account_name
},
metadata: {
seller_id: sellerId
}
});
// Save recipient ID
await db.sellers.update({
id: sellerId,
omise_recipient_id: recipient.id,
payout_enabled: true
});
res.json({
success: true,
message: 'Payout method registered'
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Process seller payout
app.post('/sellers/:sellerId/payout', async (req, res) => {
try {
const { sellerId } = req.params;
const { amount, order_ids } = req.body;
const seller = await db.sellers.findOne({ id: sellerId });
if (!seller.omise_recipient_id) {
return res.status(400).json({
error: 'Seller has not registered payout method'
});
}
// Check minimum payout
if (amount < 10000) { // เธฟ100 minimum
return res.status(400).json({
error: 'Minimum payout amount is เธฟ100'
});
}
// Create transfer
const transfer = await omise.transfers.create({
amount: amount,
recipient: seller.omise_recipient_id,
metadata: {
seller_id: sellerId,
order_ids: order_ids.join(','),
payout_date: new Date().toISOString()
}
});
// Record payout
await db.payouts.create({
seller_id: sellerId,
transfer_id: transfer.id,
amount: amount,
order_ids: order_ids,
status: 'pending',
created_at: new Date()
});
res.json({
success: true,
transfer_id: transfer.id,
status: transfer.status
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Scheduled daily payouts (runs at midnight)
cron.schedule('0 0 * * *', async () => {
console.log('Processing daily seller payouts...');
// Find sellers with pending earnings
const sellers = await db.sellers.find({
pending_earnings: { $gte: 10000 }, // เธฟ100+ minimum
payout_enabled: true
});
for (const seller of sellers) {
try {
const transfer = await omise.transfers.create({
amount: seller.pending_earnings,
recipient: seller.omise_recipient_id,
metadata: {
seller_id: seller.id,
payout_type: 'daily_automatic'
}
});
// Reset pending earnings
await db.sellers.update({
id: seller.id,
pending_earnings: 0,
last_payout_at: new Date()
});
console.log(`โ Payout to ${seller.name}: เธฟ${seller.pending_earnings / 100}`);
} catch (error) {
console.error(`โ Failed payout to ${seller.name}:`, error.message);
await notifyAdminPayoutFailed(seller, error);
}
}
});
// Webhook handler
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'transfer.pay') {
const transfer = event.data;
// Update payout status
db.payouts.update({
transfer_id: transfer.id,
status: 'completed',
completed_at: new Date()
});
// Notify seller
const sellerId = transfer.metadata.seller_id;
sendPayoutConfirmationEmail(sellerId, transfer);
}
if (event.key === 'transfer.fail') {
const transfer = event.data;
// Handle failure
db.payouts.update({
transfer_id: transfer.id,
status: 'failed',
failure_reason: transfer.failure_message
});
// Notify seller and admin
const sellerId = transfer.metadata.seller_id;
sendPayoutFailedEmail(sellerId, transfer);
notifyAdminPayoutFailed(transfer);
}
res.sendStatus(200);
});
app.listen(3000);
Scheduled Transfersโ
Create recurring automatic transfers:
// Create daily transfer schedule
const schedule = await omise.schedules.create({
every: 1,
period: 'day',
start_date: '2025-02-15',
transfer: {
recipient: recipient.id,
amount: 50000, // เธฟ500 daily
currency: 'THB'
}
});
// Create monthly transfer schedule
const monthlySchedule = await omise.schedules.create({
every: 1,
period: 'month',
on: {
days_of_month: [1] // 1st of every month
},
start_date: '2025-03-01',
transfer: {
recipient: recipient.id,
amount: 1000000, // เธฟ10,000 monthly
currency: 'THB'
}
});
Best Practicesโ
1. Validate Recipient Informationโ
async function validateRecipient(bankAccount) {
// Validate account number format
if (!/^\d{10}$/.test(bankAccount.number)) {
throw new Error('Bank account number must be 10 digits');
}
// Validate account name
if (bankAccount.name.length < 3) {
throw new Error('Account name too short');
}
// Check for duplicate
const existing = await db.recipients.findOne({
bank_account_number: bankAccount.number
});
if (existing) {
throw new Error('Recipient already registered');
}
return true;
}
2. Set Minimum Payout Thresholdsโ
const MIN_PAYOUT = {
THB: 10000, // เธฟ100
SGD: 1000, // $10
MYR: 5000 // RM50
};
function validatePayoutAmount(amount, currency) {
if (amount < MIN_PAYOUT[currency]) {
return {
valid: false,
error: `Minimum payout is ${MIN_PAYOUT[currency] / 100} ${currency}`
};
}
return { valid: true };
}
3. Handle Insufficient Balanceโ
async function createTransferSafely(transferData) {
// Check balance first
const account = await omise.account.retrieve();
const availableBalance = account.balance;
if (availableBalance < transferData.amount) {
throw new Error(`Insufficient balance. Available: เธฟ${availableBalance / 100}, Required: เธฟ${transferData.amount / 100}`);
}
// Create transfer
return await omise.transfers.create(transferData);
}
4. Batch Processingโ
async function processBatchPayouts(payouts) {
const results = [];
for (const payout of payouts) {
try {
const transfer = await omise.transfers.create({
amount: payout.amount,
recipient: payout.recipient_id
});
results.push({
payout_id: payout.id,
transfer_id: transfer.id,
status: 'success'
});
// Delay between transfers to avoid rate limits
await sleep(1000);
} catch (error) {
results.push({
payout_id: payout.id,
status: 'failed',
error: error.message
});
}
}
return results;
}
5. Retry Failed Transfersโ
async function retryFailedTransfer(originalTransferId) {
const originalTransfer = await omise.transfers.retrieve(originalTransferId);
if (originalTransfer.status !== 'failed') {
throw new Error('Only failed transfers can be retried');
}
// Create new transfer with same details
const retryTransfer = await omise.transfers.create({
amount: originalTransfer.amount,
recipient: originalTransfer.recipient,
metadata: {
...originalTransfer.metadata,
retry_of: originalTransferId,
retry_count: (originalTransfer.metadata.retry_count || 0) + 1
}
});
return retryTransfer;
}
FAQโ
How long do transfers take?
Transfer timing varies:
- Created to Sent: Within 24 hours
- Sent to Paid: 1-3 business days
- Total: 2-4 business days typically
Check specific bank processing times for your region.
What are the transfer fees?
Transfer fees vary by region and bank. Check your Omise dashboard or contact support@omise.co for current pricing.
Can I cancel a transfer?
Transfers can only be canceled while in pending status (before being sent to the bank). Use:
await omise.transfers.destroy('trsf_test_...');
What if a transfer fails?
Common failure reasons:
- Insufficient account balance
- Invalid bank account number
- Recipient account closed
- Bank reject (fraud check)
Failed transfers are not retried automatically. Review the failure message and create a new transfer after resolving the issue.
Can I transfer to international banks?
Transfer availability and supported banks vary by region. Currently, most transfers are domestic within Thailand, Singapore, and Malaysia. Contact support for international transfer capabilities.
How do I handle multiple currencies?
Create separate recipients for each currency and ensure your account balance has sufficient funds in that currency. Omise does not automatically convert currencies for transfers.
Can recipients receive multiple transfers?
Yes, once a recipient is created, you can create unlimited transfers to that recipient.
Related Resourcesโ
- Recipients API - Recipient management
- Transfers API - Transfer operations
- Schedules - Automated transfers
- Balance - Account balance management
- Webhooks - Transfer notifications
- Testing - Test transfers