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

> ## Agent Instructions
> Money amounts are always major-unit decimal strings (e.g. "1000.00"), never floats and never minor units, in every request, response, and webhook payload.
> Authentication is OAuth 2.0 client credentials: exchange the Client ID and Client Secret at the Token URL for a 24-hour JWT and cache it; do not request a token per call.
> Set a unique clientRef on every money-moving request; it is the idempotency key, and retries return the original resource.
> Quotes are single-use, intent-locked, and expire fast: always read expiresAt, consume the quote with the executor matching its intent, and re-quote on expiry instead of retrying.
> Prefer webhooks over polling for payout, order, and deposit status tracking.
> Use the sandbox (api.sandbox.zuba.com) with its deterministic magic values before touching production.

# Your First Payout

> Step-by-step guide to sending your first payout with the Zuba API, from your EUR balance to a Nigerian bank account in NGN

This guide walks you through creating and sending your first payout with the Zuba API. You'll create a beneficiary with a Nigerian bank account, then pay them NGN from your EUR balance; the currency conversion happens automatically.

## Prerequisites

Before you start, make sure you have:

* Valid API credentials (see [Authentication](/authentication))
* Your API base URL
* A Sandbox or production account with sufficient balance

## Step 1: Create a Beneficiary

First, create a beneficiary to receive the payout. A beneficiary represents a person or entity together with their payment account details.

For NGN bank accounts, `bankCode` is the 3-digit CBN short code (e.g. `044`) or the 6-digit NIP long code (e.g. `000014`), and `crAccount` is the 10-digit account number.

```typescript theme={"dark"}
const beneficiaryData = {
  name: "Adaeze Obi",
  email: "adaeze.obi@example.com",
  country: "NG",
  address: "12 Marina Road",
  city: "Lagos",
  postcode: "101233",
  accounts: [
    {
      type: "bank_account",
      currency: "NGN",
      data: {
        bankCode: "044",
        crAccount: "1234567890"
      }
    }
  ]
};

const response = await fetch('https://api.sandbox.zuba.com/v1/beneficiaries', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  },
  body: JSON.stringify(beneficiaryData)
});

const beneficiary = await response.json();
console.log('Beneficiary created:', beneficiary.id);
```

<ResponseField name="id" type="string">
  Unique UUID for the created beneficiary
</ResponseField>

<ResponseField name="accounts" type="array">
  Array of payment accounts with their IDs and details
</ResponseField>

<Tip>
  Required beneficiary fields vary by destination currency. Call
  `GET /v1/payouts/requirements` before submitting to see exactly which fields a
  currency needs, instead of discovering them through validation errors.
</Tip>

## Step 2: Create Your First Payout

Now create a payout to your beneficiary. **RECOMMENDED:** Reference the beneficiary by ID (from Step 1).

**Currency Fields:** `inputCurrency` is the currency debited from your account, while `currency` is what the beneficiary receives. When they differ, Zuba converts automatically; no separate quote is needed for this corridor:

```typescript theme={"dark"}
const payoutData = {
  clientRef: "PAYOUT-0001",
  amount: "80000.00",
  inputCurrency: "EUR",
  currency: "NGN",
  route: "bank_transfer",
  beneficiary: {
    id: beneficiary.id // Reference the beneficiary created in Step 1
  },
  reference: "Invoice payment",
  description: "Payment for services rendered"
};

const payoutResponse = await fetch('https://api.sandbox.zuba.com/v1/payouts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  },
  body: JSON.stringify(payoutData)
});

const payout = await payoutResponse.json();
console.log('Payout created:', payout.id);
```

<Note>
  `senderInfo` is optional. If omitted, your workspace is used as the business
  originator; supply it only when paying out on behalf of a distinct third
  party. Some corridors, such as mobile money in Ghana and Cameroon, require it.
</Note>

Alternatively, provide the full beneficiary object to create a new beneficiary inline:

```typescript theme={"dark"}
const payoutData = {
  clientRef: "PAYOUT-0001",
  amount: "80000.00",
  inputCurrency: "EUR",
  currency: "NGN",
  route: "bank_transfer",
  beneficiary: {
    name: "Adaeze Obi",
    email: "adaeze.obi@example.com",
    country: "NG",
    accounts: [{
      type: "bank_account",
      currency: "NGN",
      data: {
        bankCode: "044",
        crAccount: "1234567890"
      }
    }]
  },
  reference: "Invoice payment",
  description: "Payment for services rendered"
};
```

See [Payment Routes](/concepts/payouts#payment-routes) for available routes and their characteristics.

## Step 3: Monitor Payout Status

Track your payout's progress by checking its status:

```typescript theme={"dark"}
const statusResponse = await fetch(`https://api.sandbox.zuba.com/v1/payouts/${payout.id}`, {
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  }
});

const payoutDetails = await statusResponse.json();
console.log('Status:', payoutDetails.status);
```

A fresh payout moves through `created` → `queued` → `processing` → `paid`, so don't be surprised to see `created` or `queued` if you poll immediately after creation. See [Payout Statuses](/concepts/payouts#payout-statuses) for detailed status definitions.

## Step 4: Handle the Response

A successful payout response includes:

```json theme={"dark"}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "clientRef": "PAYOUT-0001",
  "reference": "Invoice payment",
  "description": "Payment for services rendered",
  "amount": "80000.00",
  "currency": "NGN",
  "route": "bank_transfer",
  "type": "fiat",
  "status": "created",
  "fee": "2.50",
  "inputCurrency": "EUR",
  "inputAmount": "52.50",
  "fxRate": "1600.00",
  "beneficiary": {
    "id": "456e7890-e89b-12d3-a456-426614174000",
    "name": "Adaeze Obi",
    "type": "individual",
    "email": "adaeze.obi@example.com",
    "country": "NG",
    "accounts": [{
      "id": "123e4567-e89b-12d3-a456-426614174001",
      "type": "bank_account",
      "currency": "NGN",
      "status": "active",
      "data": {
        "bankCode": "044",
        "crAccount": "1234567890"
      },
      "createdAt": "2026-01-15T10:25:00Z",
      "updatedAt": "2026-01-15T10:25:00Z"
    }],
    "status": "active",
    "createdAt": "2026-01-15T10:25:00Z",
    "updatedAt": "2026-01-15T10:25:00Z"
  },
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-01-15T10:30:00Z"
}
```

The FX fields show what was actually debited: `inputAmount` is the total charged in `inputCurrency` (including the fee), and `fxRate` is the rate applied to convert to the destination currency.

## Error Handling

Common errors when creating payouts:

```typescript theme={"dark"}
try {
  const response = await fetch('https://api.sandbox.zuba.com/v1/payouts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
    },
    body: JSON.stringify(payoutData)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }

  const payout = await response.json();
} catch (error) {
  console.error('Payout failed:', error.message);
  // Handle specific errors:
  // - Insufficient funds
  // - Invalid beneficiary
  // - Invalid bank code (must be a recognised CBN/NIP code)
  // - Invalid account number (crAccount must be exactly 10 digits)
  // - Currency not supported
}
```

## Next Steps

* Learn about [batch payouts](/guides/batch-payouts) for processing multiple payouts efficiently
* Set up [webhook notifications](/guides/webhooks) to receive real-time payout updates
* Explore [error handling](/guides/error-handling) for robust production implementations

## Complete Example

Here's a complete example combining all steps:

```typescript theme={"dark"}
async function sendFirstPayout() {
  const baseURL = 'https://api.sandbox.zuba.com/v1';
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  };

  try {
    // 1. Create beneficiary
    const beneficiary = await fetch(`${baseURL}/beneficiaries`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        name: "Adaeze Obi",
        email: "adaeze.obi@example.com",
        country: "NG",
        address: "12 Marina Road",
        city: "Lagos",
        postcode: "101233",
        accounts: [{
          type: "bank_account",
          currency: "NGN",
          data: {
            bankCode: "044",
            crAccount: "1234567890"
          }
        }]
      })
    }).then(r => r.json());

    console.log('Beneficiary created:', beneficiary.id);

    // 2. Create payout using beneficiary ID (recommended)
    const payout = await fetch(`${baseURL}/payouts`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        clientRef: "PAYOUT-0001",
        amount: "80000.00",
        inputCurrency: "EUR",
        currency: "NGN",
        route: "bank_transfer",
        beneficiary: {
          id: beneficiary.id // Reference the beneficiary by ID
        },
        reference: "Welcome payment",
        description: "First payout to new beneficiary"
      })
    }).then(r => r.json());

    console.log('Payout created:', payout.id);
    return payout;

  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}
```
