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

# Making a Payment

> Step-by-step guide to sending a payout through the Payments API

export const ApiExample = ({method = "GET", path, params, body, reference, tenantKey}) => {
  const baseUrl = "https://api.cadanapay.com";
  const query = params ? "?" + Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&") : "";
  const url = `${baseUrl}${path}${query}`;
  const isPlatformPath = (/^\/(v1\/)?platform(\/|$)/).test(path || "");
  const includeTenantKey = tenantKey === undefined ? !isPlatformPath : tenantKey;
  let curl = `curl -X ${method} '${url}' \\\n  -H 'Authorization: Bearer YOUR_API_KEY'`;
  if (includeTenantKey) {
    curl += ` \\\n  -H 'X-MultiTenantKey: YOUR_BUSINESS_TENANT_KEY'`;
  }
  if (body) {
    const formatted = JSON.stringify(body, null, 2);
    curl += ` \\\n  -H 'Content-Type: application/json' \\\n  -d '${formatted}'`;
  }
  return <div>
      <CodeBlock language="bash" filename="bash" wrap>
        {curl}
      </CodeBlock>
      {reference && <div style={{
    marginTop: "-0.5rem",
    marginBottom: "1rem"
  }}>
          <a href={reference} style={{
    fontSize: "0.875rem"
  }}>
            Try it in the playground →
          </a>
        </div>}
    </div>;
};

This guide walks through the complete payout flow — from discovering corridor requirements to tracking delivery.

## Prerequisites

* A Cadana business account with an **Org API key**
* Review [Authentication](/authentication) for API key setup

<Snippet file="sandbox-note.mdx" />

***

## Step 0: Fund Your Wallet

Payouts are sent from your Cadana wallet balance. Before you can send a payment, make sure your wallet is funded.

### Check Your Balance

<ApiExample method="GET" path="/v1/balances" reference="/api-reference/payments/balances/get-account-balances" />

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "517075a2-17db-469f-9481-eb8347cb920c",
      "currency": "USD",
      "balance": 703448,
      "available": 703448,
      "processing": 0
    }
  ]
}
```

Use the `available` field to determine how much you can send — `processing` reflects amounts currently in transit. All values are in the lowest denomination (e.g., cents).

### Get Funding Instructions

To add funds, call [`GET /v1/funding-details`](/api-reference/payments/balances/get-account-funding-details) to get the bank details for depositing into your wallet:

<ApiExample method="GET" path="/v1/funding-details" />

This returns bank account details you can use to transfer funds into your Cadana wallet. The response varies by currency — for example, USD returns ACH, wire, and SWIFT details, while EUR returns IBAN details.

<Tip>
  In sandbox, use [`POST /v1/sandbox/business-deposits`](/api-reference/workforce/sandbox/deposit) to add test funds to your wallet.
</Tip>

***

## Step 1: Discover Requirements

Before creating a beneficiary, query the corridor requirements and available providers.

### Get Payment Requirements

Returns the required fields for a given country, currency, and payment method:

<ApiExample method="GET" path="/v1/payment-requirements" params={{ countryCode: "KE", currency: "KES", paymentMethod: "bank" }} reference="/api-reference/payments/resources/payment-corridor-requirements" />

The response is a JSON schema describing mandatory fields, validation patterns, and allowed values. See [Payment Requirements](/payments/payment-requirements) for how to interpret the schema and build dynamic forms from it.

### Get Supported Providers

Returns banks and mobile money providers you can send payments to:

<ApiExample method="GET" path="/v1/providers" params={{ countryCode: "KE" }} reference="/api-reference/payments/resources/get-payout-providers" />

**Response:**

```json theme={null}
{
  "data": [
    {
      "code": "01",
      "name": "Example Bank",
      "countryCode": "KE",
      "currency": "KES",
      "type": "bank"
    }
  ]
}
```

Use the returned `code` as the `bankCode` when creating beneficiaries.

### Resolve a Bank Provider

If you already have a bank code, SWIFT code, or ACH routing number and need to verify the bank name and details, use [`GET /v1/providers/resolve`](/api-reference/payments/resources/resolve-bank-provider):

<ApiExample method="GET" path="/v1/providers/resolve" params={{ code: "341", currency: "BRL", paymentMethod: "bank" }} reference="/api-reference/payments/resources/resolve-bank-provider" />

***

## Step 2: Create a Beneficiary

A beneficiary stores the recipient's payment details. Create it once, then reference it for every payout to that recipient.

### Upload KYC Document

Beneficiary creation requires a KYC identity document. Upload it first:

**2a.** Get an upload URL:

<ApiExample method="POST" path="/v1/files/upload-url" body={{ purpose: "kyc-id-front", contentType: "image/jpeg" }} reference="/api-reference/payments/beneficiaries/create-upload-url" />

**Response:**

```json theme={null}
{
  "fileId": "517075a2-17db-469f-9481-eb8347cb920c",
  "putUrl": "https://cadana-kyc.s3.amazonaws.com/tmp/file_9c52fa2e?X-Amz-Algorithm=...",
  "expiresIn": 900
}
```

**2b.** Upload the file to the `putUrl` before it expires:

```bash Bash theme={null}
curl -X PUT 'RETURNED_PUT_URL' \
  -H 'Content-Type: image/jpeg' \
  --data-binary @passport-front.jpg
```

### Create the Beneficiary

<ApiExample method="POST" path="/v1/beneficiaries" body={{ name: "Jane Wanjiku", email: "jane@example.com", countryCode: "KE", currency: "KES", paymentDetails: { preferredMethod: "bank", bank: { accountName: "Jane Wanjiku", accountNumber: "1234567890", bankCode: "01", bankName: "Example Bank" } }, kyc: { firstName: "Jane", lastName: "Wanjiku", dateOfBirth: "1990-08-15", countryCode: "KE", idType: "NATIONAL_ID", idNumber: "12345678", idFileFront: "517075a2-17db-469f-9481-eb8347cb920c" } }} reference="/api-reference/payments/beneficiaries/create" />

**Response:**

```json theme={null}
{
  "id": "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b",
}
```

<Tip>
  Save the beneficiary `id`. You'll use it every time you pay this recipient.
</Tip>

### Managing Beneficiaries

Once created, you can list, retrieve, and delete beneficiaries:

| Operation | Endpoint                                                                                   |
| :-------- | :----------------------------------------------------------------------------------------- |
| List all  | [`GET /v1/beneficiaries`](/api-reference/payments/beneficiaries/list)                      |
| Get one   | [`GET /v1/beneficiaries/{beneficiaryId}`](/api-reference/payments/beneficiaries/get)       |
| Delete    | [`DELETE /v1/beneficiaries/{beneficiaryId}`](/api-reference/payments/beneficiaries/delete) |

***

## Step 3: Get an FX Quote

Lock the exchange rate before creating the payout. The returned quote `id` is required when submitting the payout.

<ApiExample method="POST" path="/v1/fx-quotes" body={{ from: "USD", to: "KES" }} reference="/api-reference/payments/fx/create-fx-quote" />

**Response:**

```json theme={null}
{
  "id": "57c74c5d-38a0-41e7-a19e-161f16dc4898",
  "from": "USD",
  "to": "KES",
  "rate": "129.50",
  "expirationTimestamp": "2024-01-15T10:45:00Z"
}
```

<Warning>
  Quotes expire in \~2 minutes. Create the payout before the quote expires or it will be rejected.
</Warning>

***

## Step 4: Create the Payout

Check your balance first, then execute:

### Check Balance

<ApiExample method="GET" path="/v1/balances" reference="/api-reference/payments/balances/get-account-balances" />

### Generate a Reference

Include a unique `reference` to identify the payout in your system. References are useful for:

* **Tracking** — look up payouts by reference via `GET /v1/payouts?reference=INV-2024-001`
* **Preventing duplicates** — submitting the same reference twice returns a `400` error, preventing accidental double payments

References are optional but recommended. Max 128 characters.

### Execute Payout

<ApiExample method="POST" path="/v1/payouts" body={{ beneficiaryId: "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b", quoteId: "57c74c5d-38a0-41e7-a19e-161f16dc4898", amount: { amount: 100000, currency: "USD" }, reference: "INV-2024-001" }} reference="/api-reference/payments/payouts/create" />

<Note>
  The `amount` value is in the lowest denomination of the currency (e.g., cents for USD). `100000` = \$1,000.00 USD.
</Note>

**Response:**

```json theme={null}
{
  "id": "d35d2bd6-188b-4e82-9a16-71442dad7375"
}
```

### Retrying a Failed Payout

If a payout fails, you can retry it by creating a new payout with the same beneficiary. You'll need a fresh FX quote (the original may have expired) and a **new reference**, since the original reference is already recorded.

```
1. Get a new FX quote      →  POST /v1/fx-quotes
2. Create a new payout      →  POST /v1/payouts (same beneficiaryId, new quoteId, new reference)
```

***

## Step 5: Track the Payment

### Webhooks (Recommended)

Subscribe to [transaction events](/reference/events#transactions) for real-time status updates. See [Webhooks](/reference/webhooks) to configure your endpoint.

Each status change emits a webhook with the following envelope:

```json theme={null}
{
  "id": "evt_abc123",
  "eventType": "transaction.initiated",
  "version": "1.0",
  "timestamp": 1681007225,
  "data": { ... }
}
```

The `data` payload varies by event:

<Tabs>
  <Tab title="transaction.initiated">
    Payout created, compliance checks in progress.

    ```json theme={null}
    {
      "id": "d35d2bd6-188b-4e82-9a16-71442dad7375",
      "amount": {
        "currency": "USD",
        "amount": 100000
      },
      "type": "PAYOUT",
      "reference": "INV-2024-001",
      "recipientId": "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b",
      "recipientType": "BUSINESS",
      "tenantKey": "your-tenant-key",
      "timestamp": 1681007225
    }
    ```
  </Tab>

  <Tab title="transaction.succeeded">
    Delivered to recipient's account.

    ```json theme={null}
    {
      "id": "d35d2bd6-188b-4e82-9a16-71442dad7375",
      "amount": {
        "currency": "USD",
        "amount": 100000
      },
      "type": "PAYOUT",
      "reference": "INV-2024-001",
      "recipientId": "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b",
      "recipientType": "BUSINESS",
      "tenantKey": "your-tenant-key",
      "timestamp": 1681007225
    }
    ```
  </Tab>

  <Tab title="transaction.failed">
    Payment failed — check the `message` field for details. If `isReturned` is `true`, the payment was previously successful but returned by the recipient's bank.

    ```json theme={null}
    {
      "id": "d35d2bd6-188b-4e82-9a16-71442dad7375",
      "message": "Account inactive",
      "isReturned": false,
      "amount": {
        "currency": "USD",
        "amount": 100000
      },
      "type": "PAYOUT",
      "reference": "INV-2024-001",
      "recipientId": "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b",
      "recipientType": "BUSINESS",
      "tenantKey": "your-tenant-key",
      "timestamp": 1681007225
    }
    ```
  </Tab>
</Tabs>

### Status Lifecycle

```
initiated → processing → succeeded
                ↓              ↓
              failed      returned (failed with isReturned: true)
```

### Polling

Alternatively, poll the payout endpoint for status updates:

<ApiExample method="GET" path="/v1/payouts/d35d2bd6-188b-4e82-9a16-71442dad7375" reference="/api-reference/payments/payouts/get" />

**Response:**

```json theme={null}
{
  "id": "d35d2bd6-188b-4e82-9a16-71442dad7375",
  "beneficiaryId": "7a7f80a6-1665-4f64-9ef3-d5f90f8f309b",
  "quoteId": "57c74c5d-38a0-41e7-a19e-161f16dc4898",
  "reference": "INV-2024-001",
  "amount": {
    "amount": 100000,
    "currency": "USD"
  },
  "sourceAmount": {
    "amount": 100000,
    "currency": "USD"
  },
  "feeAmount": {
    "amount": 500,
    "currency": "USD"
  },
  "fxRate": "129.50",
  "status": "succeeded",
  "createdTimestamp": 1748478276,
  "lastUpdatedTimestamp": 1748478276
}
```

### List Payouts

Retrieve all payouts with optional filters:

<ApiExample method="GET" path="/v1/payouts" params={{ reference: "INV-2024-001" }} reference="/api-reference/payments/payouts/list" />

The endpoint has three modes depending on which parameters you pass:

| Mode                 | When               | Behavior                                                                                                                                         |
| :------------------- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Reference lookup** | `reference` is set | Returns the single matching payout. All other params are ignored.                                                                                |
| **Paginated list**   | Default            | Returns payouts in pages (default 20). Supports `status`, `startDate`, `endDate`, `order` (`asc`/`desc`, default `desc`), `cursor`, and `limit`. |
| **CSV export**       | `format=csv`       | Returns all matching payouts as a CSV file. Requires `startDate` and `endDate`. Order is always `desc`.                                          |

***

## Complete Example

```go theme={null}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const baseURL = "https://dev-api.cadanapay.com"

func main() {
	apiKey := os.Getenv("CADANA_API_KEY")

	// 1. Upload KYC document
	fmt.Println("Uploading KYC document...")
	upload := post(apiKey, "/v1/files/upload-url", map[string]string{
		"purpose":     "kyc-id-front",
		"contentType": "image/jpeg",
	})
	fileID := upload["fileId"].(string)
	putURL := upload["putUrl"].(string)
	uploadFile(putURL, "id-front.jpg", "image/jpeg")

	// 2. Create beneficiary
	fmt.Println("Creating beneficiary...")
	ben := post(apiKey, "/v1/beneficiaries", map[string]any{
		"name":        "Jane Wanjiku",
		"email":       "jane@example.com",
		"countryCode": "KE",
		"currency":    "KES",
		"paymentDetails": map[string]any{
			"preferredMethod": "bank",
			"bank": map[string]string{
				"accountName":   "Jane Wanjiku",
				"accountNumber": "1234567890",
				"bankCode":      "01",
				"bankName":      "Example Bank",
			},
		},
		"kyc": map[string]string{
			"firstName":   "Jane",
			"lastName":    "Wanjiku",
			"dateOfBirth": "1990-08-15",
			"countryCode": "KE",
			"idType":      "NATIONAL_ID",
			"idNumber":    "12345678",
			"idFileFront": fileID,
		},
	})
	benID := ben["id"].(string)
	fmt.Printf("Beneficiary: %s\n", benID)

	// 3. Get FX quote
	fmt.Println("Getting FX quote...")
	quote := post(apiKey, "/v1/fx-quotes", map[string]string{
		"from": "USD",
		"to":   "KES",
	})
	quoteID := quote["id"].(string)
	fmt.Printf("Quote: %s (rate: %v)\n", quoteID, quote["rate"])

	// 4. Create payout
	fmt.Println("Creating payout...")
	payout := post(apiKey, "/v1/payouts", map[string]any{
		"beneficiaryId": benID,
		"quoteId":       quoteID,
		"amount":        map[string]any{"amount": 100000, "currency": "USD"},
		"reference":     fmt.Sprintf("test-%d", time.Now().Unix()),
	})
	payoutID := payout["id"].(string)
	fmt.Printf("Payout: %s\n", payoutID)

	// 5. Check status
	time.Sleep(3 * time.Second)
	fmt.Println("Checking status...")
	status := get(apiKey, "/v1/payouts/"+payoutID)
	fmt.Printf("Status: %s\n", status["status"])
}

func post(apiKey, path string, body any) map[string]any {
	data, _ := json.Marshal(body)
	req, _ := http.NewRequest("POST", baseURL+path, bytes.NewReader(data))
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")
	return doRequest(req)
}

func get(apiKey, path string) map[string]any {
	req, _ := http.NewRequest("GET", baseURL+path, nil)
	req.Header.Set("Authorization", "Bearer "+apiKey)
	return doRequest(req)
}

func doRequest(req *http.Request) map[string]any {
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Fprintf(os.Stderr, "request failed: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()
	var result map[string]any
	json.NewDecoder(resp.Body).Decode(&result)
	return result
}

func uploadFile(putURL, filePath, contentType string) {
	file, _ := os.Open(filePath)
	defer file.Close()
	req, _ := http.NewRequest("PUT", putURL, file)
	req.Header.Set("Content-Type", contentType)
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	io.Copy(io.Discard, resp.Body)
}
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Payment Methods & Requirements" icon="list-check" href="/payments/payment-requirements">
    Supported methods and required fields per corridor
  </Card>

  <Card title="RFI & Compliance" icon="shield-check" href="/payments/rfi-compliance">
    Handling compliance holds and document requests
  </Card>

  <Card title="Testing" icon="flask-vial" href="/reference/sandbox#simulated-payment-failures">
    Simulate payment scenarios in sandbox
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/payments">
    Full endpoint documentation
  </Card>
</CardGroup>
