Skip to main content
POST
/
v1
/
payout-api
/
payouts
/
initiate
Initiate payout
curl --request POST \
  --url https://gateway.useyala.com/v1/payout-api/payouts/initiate \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'x-api-key: <api-key>' \
  --data '
{
  "businessId": "6fd2b69f-c529-4fe4-b680-29256f860553",
  "sourceAmount": 1000,
  "sourceCurrency": "USD",
  "destinationCurrency": "NGN",
  "method": "NIP",
  "destinationCountryCode": "NGA",
  "narration": "Andromeda Galaxy",
  "beneficiary": {
    "countryCode": "NGA",
    "bankCountryCode": "NGA",
    "accountNumber": "8068487823",
    "accountName": "Will Wonker",
    "bankName": "OPay",
    "accountCurrency": "NGN",
    "payoutMethod": "SWIFT",
    "isIndividual": true,
    "address": "2, 4th Avenue, Off Prince Ademola Eletu way, Jakande, Lekki",
    "city": "Lekki",
    "postCode": "SG1 5LH",
    "swiftNumber": "34323232",
    "routingNumber": "",
    "sortCode": "111111",
    "phoneNumber": "",
    "email": "",
    "bankBranchCode": "",
    "bankAddress": "",
    "bankCode": "34323232",
    "bicNumber": "34323232"
  },
  "supportingDocument": "https://sample.com/doc"
}
'

Overview

Creates a payout for external partners via API. The system automatically handles FX conversion:
  • No pre-funding required - source currency can differ from destination currency
  • FX conversion happens automatically
  • Payouts start in PENDING status
Supporting Documents: Supporting documents (invoices, receipts, contracts) are required for every payout via the API. This helps:
  • Faster compliance review - Payouts with documents are processed faster
  • Reduced delays - Compliance team can verify transactions immediately
  • Better approval rates - Complete documentation reduces rejection risk

Required Headers

HeaderRequiredDescription
Idempotency-KeyYesClient-generated unique key to prevent duplicate payouts. Generate using UUID or order ID before making the request.
x-api-keyYesYour API key
Idempotency-Key is required. Missing header returns 400 Bad Request. Generate the key client-side before making the API call.

Request

POST /v1/payout-api/payouts/initiate HTTP/1.1
Host: gateway.staging.useyala.com
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
x-api-key: <YOUR_API_KEY>

Request Body

{
  "sourceAmount": 10000,
  "sourceCurrency": "NGN",
  "destinationCurrency": "CNY",
  "destinationCountryCode": "CHN",
  "method": "ALIPAY",
      "beneficiary": {
        "accountName": "Li Wei",
        "accountCurrency": "CNY",
        "alipayId": "user@alipay.com"
      },
      "narration": "Payment for services",
      "supportingDocument": "https://yala-staging.s3.us-east-1.amazonaws.com/1769262715606-Logo.png"
    }

Request Fields

FieldTypeRequiredDescription
sourceAmountnumberYesAmount to debit from source wallet
sourceCurrencystringYesSource wallet currency (e.g., “NGN”, “USD”). Debit is always from this currency’s wallet—the system debits the business wallet that matches this currency.
destinationCurrencystringYesDestination currency (e.g., “CNY”, “NGN”)
destinationCountryCodestringYesISO country code (3-letter ISO 3166-1 alpha-3, e.g., “CHN” for China, “NGA” for Nigeria, “USA” for United States)
methodstringYesPayment method (e.g., “ALIPAY”, “SWIFT”, “NIP”)
beneficiaryobjectYesBeneficiary details (fields depend on method - see /methods endpoint)
narrationstringYesPayment description
supportingDocumentstring (URL)YesSupporting document URL (HTTPS required). Required: Upload invoices or receipts for every payout to expedite compliance review and faster payment processing.
Beneficiary Fields: The beneficiary object has a unified shape - all fields are optional except accountName and accountCurrency. Required fields vary by payment method and are validated at the service level. Key Points:
  • Same structure for all methods - The beneficiary object always has the same fields available
  • Method-specific requirements - Use GET /methods endpoint to discover which fields are required for your payment method
  • Automatic country derivation - If beneficiary.countryCode is not provided, it will be automatically derived from destinationCountryCode in the request body
  • Service-level validation - The API validates that all required fields for your selected method are present
Example: For ALIPAY, you only need accountName, accountCurrency, and alipayId. All other fields can be omitted.

Response

{
  "id": "payout-id-uuid",
  "payoutRef": "PAY-2024-001",
  "businessId": "business-id-uuid",
  "sourceAmount": 10000,
  "sourceCurrency": "NGN",
  "destinationAmount": 42.5,
  "destinationCurrency": "CNY",
  "exchangeRate": 235.0,
  "transactionFees": 0,
  "status": "PENDING",
  "method": "ALIPAY",
  "narration": "Payment for services",
  "createdAt": "2024-01-20T10:00:00Z",
  "updatedAt": "2024-01-20T10:00:00Z"
}

Response Fields

FieldTypeDescription
idstring (UUID)Payout identifier - Save this ID to track status via GET /:id or webhook notifications
payoutRefstringHuman-readable payout reference
sourceAmountnumberAmount debited from source wallet
sourceCurrencystringSource currency
destinationAmountnumberAmount sent to beneficiary (after FX conversion)
destinationCurrencystringDestination currency
exchangeRatenumberActual exchange rate used for conversion - This is the rate that was applied at the time of payout initiation. If you previously viewed rates via GET /pairs or GET /methods, those rates were for reference only. Always check this field to confirm the rate that was actually used.
transactionFeesnumberTransaction fees (currently 0)
statusstringOne of: PENDING, PROCESSING, SUCCESSFUL, FAILED, REJECTED
methodstringPayment method used
narrationstringPayment description
createdAtstring (ISO 8601)Payout creation timestamp
updatedAtstring (ISO 8601)Last update timestamp
Important: Save the id field from this response. Use it to:
  • Query payout status via GET /v1/payout-api/payouts/{id}
  • Match webhook notifications (webhook payload includes payoutId matching this id)

Idempotency

How it works:
  1. Generate key client-side before making the request:
    const idempotencyKey = crypto.randomUUID();
    // OR
    const idempotencyKey = `order-${orderId}`;
    
  2. Include in header:
    headers: {
      'Idempotency-Key': idempotencyKey,
    }
    
  3. Retry with same key if request fails:
    • Network error/timeout: Retry with same key + same body
    • API returns original payout (no duplicate created)
Behavior:
  • Same key + same request body: Returns existing payout (no duplicate)
  • Same key + different request body: 409 Conflict (use a new key)
  • Missing key: 400 Bad Request

Error Responses

400 Bad Request - Missing Idempotency Key

{
  "statusCode": 400,
  "message": "Idempotency-Key header is required",
  "error": "MISSING_IDEMPOTENCY_KEY"
}

400 Bad Request - Missing Required Fields

{
  "statusCode": 400,
  "message": "Missing required fields: alipayId",
  "error": "MISSING_REQUIRED_FIELDS"
}
Solution: Use GET /methods endpoint to get required fields for your payment method. Required fields vary by method:
  • ALIPAY: accountName, alipayId
  • WECHAT: accountName, and one of: openId OR wechatUserId
  • SEPA: accountName, iban
  • SWIFT: accountName, accountNumber, swiftCode (required), bankName, beneficiaryCountry (required), iban (optional), intermediarySwift (optional), address (optional), city (optional), postCode (optional)
  • ACH: accountName, routingNumber, accountNumber, accountType (enum: “checking” or “savings”)
  • NIP: accountName, accountNumber, bankCode
  • BACS/FASTER_PAYMENTS: accountName, sortCode, accountNumber
  • HONG_KONG_FPS: accountName, and one of: fpsId OR phoneNumber OR email OR (accountNumber + bankCode)

400 Bad Request - Insufficient Funds

{
  "statusCode": 400,
  "message": "Insufficient balance in source wallet",
  "error": "INSUFFICIENT_FUNDS"
}

409 Conflict - Idempotency Key Reused

{
  "statusCode": 409,
  "message": "Idempotency key already used with a different request. Use a new key or retry with the same request body.",
  "error": "IDEMPOTENCY_KEY_CONFLICT"
}
Solution: Generate a new idempotency key for this payout, or retry with the exact same request body.

401 Unauthorized

{
  "statusCode": 401,
  "message": "Unauthorized"
}

Usage Example

// 1. Generate idempotency key (client-side)
const idempotencyKey = crypto.randomUUID();

// 2. Get required fields for method (optional, if you need to validate)
const methodsResponse = await fetch(
  'https://gateway.staging.useyala.com/v1/payout-api/payouts/methods?sourceCurrency=NGN&destinationCurrency=CNY&destinationCountryCode=CHN',
  {
    headers: { 'x-api-key': apiKey }
  }
);
const { methods } = await methodsResponse.json();
const alipayMethod = methods.find(m => m.code === 'ALIPAY');

// 3. Create payout
const response = await fetch(
  'https://gateway.staging.useyala.com/v1/payout-api/payouts/initiate',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,  // Client-generated
      'x-api-key': apiKey,
    },
    body: JSON.stringify({
      sourceAmount: 10000,
      sourceCurrency: 'NGN',
      destinationCurrency: 'CNY',
      destinationCountryCode: 'CHN',
      method: 'ALIPAY',
      beneficiary: {
        accountName: 'Li Wei',
        accountCurrency: 'CNY',
        alipayId: 'user@alipay.com',
      },
      narration: 'Payment for services',
      supportingDocument: 'https://example.com/invoice.pdf',
    }),
  }
);

const payout = await response.json();
console.log(`Payout created: ${payout.id}, Status: ${payout.status}`);

// 4. Store payout.id for tracking
// Use payout.id to query status via GET /:id or receive webhook notifications

// 5. If request fails, retry with SAME key + SAME body
// API will return the original payout (no duplicate)
Save the id field from the response - Use this payout ID to track status via GET /:id or receive webhook notifications when status changes.

Webhooks

When a payout’s status changes, we send a webhook notification to your configured endpoint. The webhook payload includes the payout id so you can track which payout was updated. To enable webhooks:
  1. Activate webhook notifications in your Yala dashboard
  2. Configure your webhook endpoint URL
  3. You’ll receive payout.status.changed.v1 events when status changes
Webhook payload includes:
  • payoutId - The payout ID (use this to match with your records)
  • oldStatus - Previous status
  • newStatus - New status (one of PENDING, PROCESSING, SUCCESSFUL, FAILED, REJECTED)
  • changedAt - Timestamp of status change
  • reason - Human-readable reason for the change

Exchange Rate Behavior

Important: The exchange rate used for conversion is determined at the time of payout initiation.
  • Rate is fetched fresh - When you call POST /initiate, the system fetches the current exchange rate from our rate database
  • Rate may differ from preview - If you previously viewed rates via GET /pairs or GET /methods, those rates were for reference only. The actual rate applied may be different if rates changed between viewing and initiating
  • Check the response - The exchangeRate field in this response shows the rate that was actually used for your payout
  • Rate is not locked - There is no rate guarantee or locking mechanism. The rate used is whatever is current at initiation time
Best Practice: Always display the exchangeRate from the /initiate response to your users to show them the actual rate that was applied, rather than relying on rates shown in preview endpoints.

Notes

  • FX Conversion: Happens automatically (no pre-funding required)
  • Status: Payouts start in PENDING status
  • Field Validation: Beneficiary fields are validated against method requirements
  • Tracking: Save the id field from the response to track payout status
  • Fees: Currently disabled
  • Exchange Rate: Always check the exchangeRate field in the response to see the rate that was actually applied

Authorizations

x-api-key
string
header
required

Headers

Idempotency-Key
string
required
Example:

"550e8400-e29b-41d4-a716-446655440000"

Body

application/json
businessId
string<uuid>
required

Unique identifier for the business

Example:

"6fd2b69f-c529-4fe4-b680-29256f860553"

sourceAmount
number
required

Amount in the source currency

Example:

1000

sourceCurrency
string
required

Source currency code

Example:

"USD"

destinationCurrency
string
required

Destination currency code

Example:

"NGN"

method
enum<string>
required

Payment method

Available options:
SWIFT,
NIP,
HK_FPS,
CHINA_WIRE,
FASTER_PAYMENTS,
WIRE,
ACH,
SEPA
Example:

"NIP"

destinationCountryCode
string
required

Destination country code

Example:

"NGA"

narration
string
required

Transaction description

Example:

"Andromeda Galaxy"

beneficiary
object
required
supportingDocument
string<uri>

URL to supporting document

Example:

"https://sample.com/doc"

Response

Payout initiated successfully