> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useyala.com/llms.txt
> Use this file to discover all available pages before exploring further.

# End-to-End Integration Flow

> Understand how to integrate Yala payout APIs into your application for your end users.

## Overview

This guide walks through the complete flow of integrating Yala payout APIs into your application, from your end user's perspective to your backend implementation.

## Integration Flow

The integration follows this pattern:

1. User initiates payout in your app
2. Your frontend sends request to your backend
3. Your backend calls Yala API
4. Yala processes and responds
5. Your backend updates your database
6. Your frontend displays status to user
7. User sees updated status

## Step-by-Step Flow

### 1. Your user selects a payout corridor

**What happens:**

* User visits your app/website
* User selects source currency (e.g., NGN) and destination currency (e.g., CNY)
* User enters amount they want to send

**Your implementation:**

* Display available currency pairs (you can cache this or call GET /pairs periodically)
* Call **GET /wallets** to show the user's balance per currency. **Debit is always from the source currency wallet**—when they initiate a payout in NGN, the NGN wallet is debited.
* Validate user has sufficient balance in the selected source currency before calling initiate

### 2. Your backend calls Yala API to get rates and methods

**What happens:**

* Your backend calls GET /methods with the selected corridor
* Yala returns current exchange rate, available payment methods, and required beneficiary fields

**Your implementation:**

```javascript theme={null}
// Your backend: GET /methods to show rates and payment options
const url = 'https://gateway.staging.useyala.com/v1/payout-api/payouts/methods?sourceCurrency=NGN&destinationCurrency=CNY&destinationCountryCode=CHN';
const methodsResponse = await fetch(url, { 
  headers: { 
    'x-api-key': apiKey
  } 
});

const { methods, rate, corridor } = await methodsResponse.json();

// Return to your frontend:
// - rate.rate (exchange rate)
// - methods array (each with code, name, settlement, fee, requiredFields, optionalFields)
```

### 3. Your frontend displays options to user

**What happens:**

* Your frontend receives rates and methods from your backend
* User sees exchange rate and available payment methods
* User selects a payment method (e.g., ALIPAY)

**Your implementation:**

* Display exchange rate: "1 NGN = \[rate.rate] CNY" (replace \[rate.rate] with the actual rate value from the response)
* Show available methods with settlement times and fees
* Let user select preferred method
* Show estimated destination amount: sourceAmount multiplied by the rate value

### 4. User fills beneficiary form

**What happens:**

* Your frontend renders a dynamic form based on requiredFields from the selected method
* User enters beneficiary details (name, Alipay ID, address, etc.)
* User confirms payout

**Your implementation:**

```javascript theme={null}
// Get required fields for selected method
const selectedMethod = methods.find(m => m.code === 'ALIPAY');

// Render form fields dynamically
selectedMethod.requiredFields.forEach(field => {
  // Create input based on field.type (string, email, phone, number)
  // Apply validation based on field.validation (e.g., "email_or_phone")
  // Mark as required if field.required === true
});

// Also include optionalFields if you want to collect them
```

**Form fields example:**

* accountName (string, required)
* alipayId (string, required, validation: "email\_or\_phone")
* address (string, required)
* city (string, required)
* postCode (string, required)
* email (email, optional)
* phoneNumber (phone, optional)

### 4a. (Recommended) Show quote before confirm

**What happens:**

* Before the user clicks "Confirm", call **POST /quote** with the same amount, currencies, and method
* Yala returns `totalDeductible` (amount debited from wallet), `destinationAmount` (amount beneficiary receives), and `includedFees`
* Your frontend shows: "You will pay X. Recipient gets Y. Fee: Z."

**Your implementation:**

* Call `POST /v1/payout-api/payouts/quote` with `sourceAmount`, `sourceCurrency`, `destinationCurrency`, `destinationCountryCode`, and `method`
* Display `quote.totalDeductible`, `quote.destinationAmount`, and `quote.includedFees` to the user
* When the user confirms, proceed to POST /initiate (see [Get Payout Quote](/api-reference/endpoint/quote))

### 5. Upload supporting document

For most payouts, attach an invoice. Payouts without an invoice **will not be processed**, except **withdrawals** to your business's own account or to a UBO's account.

Upload **before or after** creating the payout:

1. `POST /documents/upload-url` to get `uploadUrl` and `documentUrl`
2. `PUT` file bytes to **`uploadUrl`** (S3 — not Yala)
3. Pass **`documentUrl`** on initiate or PATCH

See [Uploading Supporting Documents](/guides/uploading-supporting-documents).

### 6. Your backend creates payout

**What happens:**

* Your backend calls POST /initiate with beneficiary details
* Yala creates payout and returns payout details including id
* Your backend saves payout.id to your database

**Your implementation:**

```javascript theme={null}
// Optional: upload document first (see guide above), then set documentUrl below
let documentUrl = null;
if (userInvoiceFile) {
  const metaRes = await fetch(
    'https://gateway.staging.useyala.com/v1/payout-api/payouts/documents/upload-url',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
      body: JSON.stringify({
        filename: 'invoice.pdf',
        contentType: 'application/pdf',
      }),
    }
  );
  const { uploadUrl, documentUrl: stableUrl, headers } = await metaRes.json();
  await fetch(uploadUrl, { method: 'PUT', headers, body: userInvoiceFile });
  documentUrl = stableUrl;
}

// POST /initiate
const idempotencyKey = crypto.randomUUID(); // Generate unique key per payout

const payoutBody = {
  sourceAmount: userEnteredAmount,
  sourceCurrency: 'NGN',
  destinationCurrency: 'CNY',
  destinationCountryCode: 'CHN',
  method: 'ALIPAY',
  beneficiary: {
    accountName: formData.accountName,
    accountCurrency: 'CNY',
    alipayId: formData.alipayId,
    address: formData.address,
    city: formData.city,
    postCode: formData.postCode,
    email: formData.email,
    phoneNumber: formData.phoneNumber,
  },
  narration: 'Payment for services',
};
if (documentUrl) {
  payoutBody.supportingDocument = documentUrl;
}

const payoutResponse = await fetch(
  'https://gateway.staging.useyala.com/v1/payout-api/payouts/initiate',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey, // Required!
      'x-api-key': apiKey,
    },
    body: JSON.stringify(payoutBody),
  }
);

const payout = await payoutResponse.json();

// IMPORTANT: Save payout.id to your database
// Link it to your order/transaction record
await saveToDatabase({
  orderId: userOrderId,
  payoutId: payout.id, // Use this to track status
  status: payout.status,
  createdAt: new Date(),
});
```

**Response includes:**

* id - **Save this!** Use for tracking and webhook matching
* status - Initial status (PENDING)
* sourceAmount, destinationAmount - Conversion details
* exchangeRate - Rate used for conversion
* payoutRef - Human-readable reference

### 7. Track payout status

**Option A: Polling (Simple but less efficient)**

```javascript theme={null}
// Your backend: Periodically check status
const statusUrl = 'https://gateway.staging.useyala.com/v1/payout-api/payouts/' + payoutId;
const statusResponse = await fetch(statusUrl, {
  headers: { 'x-api-key': apiKey }
});

const status = await statusResponse.json();
// Update your database with status.status
// Notify your frontend/user of status change
```

**Option B: Webhooks (Recommended for production)**

1. **Activate webhooks** in your Yala dashboard
2. **Configure your webhook endpoint** URL
3. **Receive webhook notifications** when status changes

```javascript theme={null}
// Your webhook endpoint (receives POST from Yala)
app.post('/webhooks/yala', async (req, res) => {
  const { eventType, deliveryId, data } = req.body;
  const { payoutId, oldStatus, newStatus, changedAt, reason } = data || {};
  
  // Find payout in your database by payoutId
  const order = await findOrderByPayoutId(payoutId);
  
  // Update status in your database (newStatus is one of PENDING, ADDITIONAL_INFO_REQUIRED, PROCESSING, SUCCESSFUL, FAILED, REJECTED)
  await updateOrderStatus(order.id, newStatus);
  
  // Notify your frontend/user (via WebSocket, push notification, etc.)
  notifyUser(order.userId, {
    payoutId,
    status: newStatus,
    reason,
  });
  
  res.status(200).send('OK');
});
```

**Webhook payload:** We POST a JSON body with `eventType`, `deliveryId`, `occurredAt`, and `data`. Status values in `data.oldStatus` and `data.newStatus` are always one of the six below:

```json theme={null}
{
  "eventType": "payout.status.changed.v1",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "occurredAt": "2024-01-20T10:05:00Z",
  "data": {
    "payoutId": "123e4567-e89b-12d3-a456-426614174000",
    "businessId": "business-id",
    "oldStatus": "PENDING",
    "newStatus": "PROCESSING",
    "changedAt": "2024-01-20T10:05:00Z",
    "changedBy": "system",
    "reason": "Payout approved and processing"
  }
}
```

### 7. User sees status updates

**What happens:**

* Your backend receives webhook or polls status
* Your frontend displays status to user
* Status transitions: PENDING → ADDITIONAL\_INFO\_REQUIRED → PENDING → PROCESSING → SUCCESSFUL (ADDITIONAL\_INFO\_REQUIRED appears when additional information is requested)

**Your implementation:**

* Update your database when status changes (use `data.newStatus` from webhook payload)
* Push status update to your frontend (WebSocket, polling, or push notification)
* Display status in user's dashboard/order page

**Status values (same in API and webhooks):**

* PENDING - Payout created or awaiting approval; additional info submitted and under review; not yet sent for processing
* ADDITIONAL\_INFO\_REQUIRED - Additional information has been requested for this payout
* PROCESSING - Payout is being processed (ops, bank, or treasury)
* SUCCESSFUL - Completed; funds have been sent
* FAILED - Processing failed (e.g. bank failure)
* REJECTED - Rejected (e.g. compliance, initiation checks, or bank)

## Key Integration Points

### Store payout.id

**Always save payout.id from the initiate response:**

* Link it to your order/transaction record
* Use it to query status via GET /:id
* Match it with webhook notifications (webhook payload includes payoutId)

### Idempotency

**Generate idempotency key per payout:**

* Use UUID or order-\[orderId] format (replace \[orderId] with your actual order ID)
* Store with your order record
* Retry with same key + same body if request fails (prevents duplicates)

### Error Handling

**Handle common errors:**

* INSUFFICIENT\_FUNDS - User's wallet balance too low
* CORRIDOR\_NOT\_ENABLED - Currency pair not enabled in dashboard
* MISSING\_REQUIRED\_FIELDS - Beneficiary form incomplete
* IDEMPOTENCY\_KEY\_CONFLICT - Same key used with different request

### Webhook Security

**Validate webhook requests:**

* Verify source IP (Yala will provide IP ranges)
* Verify shared secret (provided during webhook setup)
* Implement idempotency for webhook processing (avoid duplicate processing)

<Tip>
  See the [Quickstart guide](/quickstart) for a complete code example combining all these steps.
</Tip>

## Next Steps

* [Quickstart](/quickstart) - Get started with your first payout
* [API Reference](/api-reference/introduction) - Explore all endpoints
* [Error Handling](/guides/errors) - Handle errors gracefully
* [Authentication](/guides/authentication) - Secure your API calls
