> For the complete documentation index, see [llms.txt](https://docs.fonbnk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fonbnk.com/countries.md).

# Countries

One page per country and payment channel: channels, limits, KYC rules, quirks and API examples.

Each country page lists its payment channels, its quirks, how to verify a user, and its limits, KYC rules and fees. Each payment channel has its own page that walks through a real order, with the country values filled in.

| Region          | Countries                                                                                                                                                                                                                                            |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| West Africa     | [Nigeria](/countries/nigeria.md), [Ghana](/countries/ghana.md), [Ivory Coast](/countries/ivory-coast.md), [Senegal](/countries/senegal.md), [Benin](/countries/benin.md), [Burkina Faso](/countries/burkina-faso.md), [Gambia](/countries/gambia.md) |
| East Africa     | [Kenya](/countries/kenya.md), [Tanzania](/countries/tanzania.md), [Uganda](/countries/uganda.md), [Rwanda](/countries/rwanda.md)                                                                                                                     |
| Central Africa  | [Cameroon](/countries/cameroon.md), [Gabon](/countries/gabon.md), [Republic of the Congo](/countries/republic-of-the-congo.md), [DR Congo](/countries/dr-congo.md)                                                                                   |
| Southern Africa | [South Africa](/countries/south-africa.md), [Zambia](/countries/zambia.md), [Malawi](/countries/malawi.md)                                                                                                                                           |
| Latin America   | [Brazil](/countries/brazil.md)                                                                                                                                                                                                                       |
| South East Asia | [Philippines](/countries/philippines.md)                                                                                                                                                                                                             |

The crypto networks and assets are on [Supported countries and cryptocurrencies](/supported-countries-and-cryptocurrencies.md).

## Before you start

* **Keys.** Sandbox and production are separate systems with separate keys. The examples call sandbox, `https://sandbox-api.fonbnk.com`. Switch `FONBNK_BASE_URL` to `https://api.fonbnk.com` and your production keys to go live. See [Servers](/server-to-server/servers.md).
* **The create-users permission.** Get user KYC state, Submit user KYC, Create order and Confirm order answer `403` without it, even when quotes work. Ask support to switch it on.
* **A webhook URL** in the merchant dashboard. The order statuses arrive there. See [Webhooks](/server-to-server/webhooks.md).
* **A unique `orderParams` for each order.** It is your own reference. You need it to recover from a timeout, see "Retries and errors" below.

Every call is signed as [Signing requests](/server-to-server/signing-requests.md) explains. The examples on the country pages use these helpers:

{% tabs %}
{% tab title="curl" %}
{% code title="fonbnk.sh" overflow="wrap" %}

```bash
export FONBNK_BASE_URL='https://sandbox-api.fonbnk.com'  # https://api.fonbnk.com in production
export FONBNK_CLIENT_ID='<your client id>'
export FONBNK_CLIENT_SECRET='<your api secret>'

# Prints the x-signature of one request: HMAC-SHA256 over "timestamp:endpoint", base64.
fonbnk_sign() {
  local key_hex
  key_hex=$(printf '%s' "$FONBNK_CLIENT_SECRET" | base64 --decode | xxd -p -c 256)
  printf '%s' "$1:$2" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$key_hex" -binary | base64
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="fonbnk.ts" overflow="wrap" %}

```typescript
import crypto from 'node:crypto';

const BASE_URL = process.env.FONBNK_BASE_URL ?? 'https://sandbox-api.fonbnk.com';
const CLIENT_ID = process.env.FONBNK_CLIENT_ID ?? '';
const CLIENT_SECRET = process.env.FONBNK_CLIENT_SECRET ?? '';

/**
 * Signs and sends one Merchant API call. `endpoint` is the path and the query string.
 * Throws on a non-2xx answer, with the error body, so the caller can read `errorCode`.
 * Do not retry Create order blindly on a timeout: see "Retries and errors" below.
 */
export const fonbnkRequest = async (method: 'GET' | 'POST', endpoint: string, body?: unknown) => {
  const timestamp = Date.now().toString();
  const signature = crypto
    .createHmac('sha256', Buffer.from(CLIENT_SECRET, 'base64'))
    .update(`${timestamp}:${endpoint}`)
    .digest('base64');
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'x-client-id': CLIENT_ID,
      'x-timestamp': timestamp,
      'x-signature': signature,
    },
    body: body === undefined ? undefined : JSON.stringify(body),
    signal: AbortSignal.timeout(30_000),
  });
  const responseBody = await response.json();
  if (!response.ok) {
    throw Object.assign(new Error(responseBody.message), { status: response.status, body: responseBody });
  }
  return responseBody;
};
```

{% endcode %}
{% endtab %}
{% endtabs %}

## After the order

Branch on the status you receive, not on the one you expect next: statuses can be skipped. The full list is on [Order statuses](/server-to-server/order-statuses.md).

| Status                                                       | What you do                                                                      |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| `deposit_awaiting`                                           | Show `transferInstructions` to the user.                                         |
| `deposit_validating`, `deposit_successful`, `payout_pending` | Wait. The payment is being checked or the payout is being sent.                  |
| `payout_successful`                                          | Final. Deliver what the user paid for.                                           |
| `deposit_canceled`, `deposit_expired`, `deposit_invalid`     | Not final. A late payment can still succeed, so do not reverse your records yet. |
| `payout_failed`                                              | Not final. We retry the payout, or refund the user.                              |
| `refund_initiated`, `refund_pending`, `refund_failed`        | Wait for the refund.                                                             |
| `refund_successful`                                          | Final. The user has the money back.                                              |

Your webhook endpoint must:

* check `x-signature` against the raw body before it acts, see [Webhooks](/server-to-server/webhooks.md#verifying-the-signature);
* answer `2xx` within 20 seconds, at the exact URL you registered, with no redirect;
* ignore a status it already handled, because a retry can deliver a status twice and out of order;
* match the order by `_id` or `merchantOrderParams`, and call Get order when it needs more than the webhook carries.

## Retries and errors

A refused call answers with `message`, `errorCode` and `details`. Branch on `errorCode`, not on the text. All codes are on [Errors](/server-to-server/errors.md).

* **Create order is not idempotent.** A second call with the same body creates a second order, and a repeated `orderParams` is accepted. After a timeout, call Get order with your `orderParams` first: `GET /api/v2/order?orderParams=<your reference>`. Retry only when it answers `404`.
* **`ROUTE_NOT_AVAILABLE`** is the only "no offer" code worth retrying. Every other `404` code fails again unchanged.
* **`429`** carries a `Retry-After` header in seconds. Wait that long.
* **An expired quote** cannot create an order. Get a new quote. Its fields and options can differ from the old one.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.fonbnk.com/countries.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
