# Intro

## Introduction

Welcome to the Fonbnk Pay Widget Documentation!

### Overview

The Fonbnk Pay Widget is a secure and efficient way to facilitate both on-ramp and off-ramp transactions in crypto. It supports integrated and non-integrated methods, making it versatile for various use cases. This documentation will guide you through the setup, integration, and usage of the Fonbnk Pay Widget.

### Key Features

* **P2P Platform**: Connects buyers and sellers of mobile money (Airtime, Mobile Money, Bank, etc.).
* **Crypto Payments**: Facilitates transactions in crypto.
* **Multiple Integration Options**: Supports standalone, iframe/webview, and webhook integrations.
* **Customizable**: Configure the widget using URL parameters to suit your needs.

### How on-ramp works

1. **Customer Selection**: The customer selects the source of their fund and the amount of crypto they want to receive.
2. **Wallet Details**: The customer provides their wallet details.
3. **Funds Transfer**: The customer transfers funds to an agent and confirms the order.
4. **Order Confirmation**: The agent confirms the order, and the system sends crypto to the customer's wallet.

### How off-ramp works

Fonbnk Pay Widget also supports off-ramp transactions, allowing users to convert their crypto back into traditional fiat currency. Here’s how the off-ramp process works for end users:

1. **Customer Selection**: The customer selects the amount of crypto they want to convert to fiat.
2. **Wallet Details**: The customer provides their wallet details for receiving the crypto.
3. **Funds Transfer**: The customer transfers the crypto to an agent and confirms the order.
4. **Order Confirmation**: The agent confirms the receipt of crypto, and the system initiates the transfer of fiat currency to the customer's bank account or other specified method.

This process ensures a secure and efficient way for users to convert their crypto holdings into fiat currency.

### Getting Started

To get started, choose the integration type that best suits your needs and follow the detailed guides provided in this documentation. Whether you are setting up a simple donation link or a complex merchant integration, the Fonbnk Pay Widget offers a flexible solution for accepting and converting crypto payments.

Explore the documentation to learn more about the features, configurations, and best practices for using the Fonbnk Pay Widget.


# Integration Guide

### Video tutorial <a href="#setting-up-your-sandbox-environment" id="setting-up-your-sandbox-environment"></a>

{% embed url="<https://vimeo.com/1082484387>" %}

### Setting Up Your Sandbox Environment <a href="#setting-up-your-sandbox-environment" id="setting-up-your-sandbox-environment"></a>

To begin integrating with our system, the first step is to register a merchant account in the sandbox environment. Follow this link to initiate the registration process: <https://sandbox-dashboard.fonbnk.com/register-initiate>.<br>

Configuring Webhook Integration

Once you have a sandbox account, navigate to the **Settings** page on the dashboard. Here, you can configure a webhook URL to receive notifications regarding order status changes.

<figure><img src="/files/WLp7NY8uq3jqDU4aPxoT" alt=""><figcaption><p>Webhook setup in the merchant dashboard</p></figcaption></figure>

{% hint style="info" %}
Learn more about the webhook structure and signature [here](/v1.5/on-ramp/webhook).
{% endhint %}

You can also test your webhook integration using the **Simulate the webhook request** feature. Provide a URL and click the **Send Request** button to have the dashboard send a test notification to the specified URL.

<figure><img src="/files/59XUJmIHX8yrnA6ZJRY5" alt=""><figcaption><p>Webhook simulation in the merchant dashboard</p></figcaption></figure>

{% hint style="info" %}
If you want to preview webhook notifications without setting up a server, you can use the [webhooks service](https://webhook.site/).
{% endhint %}

### Generating Payment URLs and Creating Orders <a href="#generating-payment-urls-and-creating-orders" id="generating-payment-urls-and-creating-orders"></a>

To create sandbox orders, utilize the sandbox pay widget, which can be accessed at [Sandbox Pay Widget](https://sandbox-pay.fonbnk.com/). To associate an order with your merchant account, you must include the **source** parameter in the pay widget URL. You can find the **source** parameter value in the **Additional Details** section of the **Settings** page on the dashboard.

<figure><img src="/files/8baGHxHiA0Rh05lpzVD3" alt=""><figcaption><p>Source param in the merchant dashboard</p></figcaption></figure>

Additionally, you must provide a unique **signature** parameter, which is a JWT token (HS256 encryption algorithm) generated using "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow to create more than 1 order using the same signature. During testing, you can generate a JWT signature using this website, <https://jwt.io/>. You can also provide [URL configuration parameters](/v1.5/on-ramp/url-parameters) in the JWT token payload.\
&#x20;

An example of a token generation in typescript:

```typescript
import * as jsonwebtoken from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';

const token = jsonwebtoken.sign(
    {
      uid: uuid(),
    },
    YOUR_SIGNATURE_SECRET,
    {
      algorithm: 'HS256',
    },
 );
```

With the provided **source** parameter, the pay widget URL will look like this: <https://sandbox-pay.fonbnk.com/?source=bd3X9Cgq&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJlcmcyMmYyZkBAIn0.Z1BB4eiClKH_k18w5I3tMiutuWpPgPb5gI33FrkpJcY>.

To create an order in the sandbox environment, you must use one of the following accounts if you want an order to be automatically confirmed.

<table><thead><tr><th>Country</th><th>Email</th><th width="170">Password</th></tr></thead><tbody><tr><td>Nigeria</td><td>sandbox-ng@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Kenya</td><td>sandbox-ke@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Ghana</td><td>sandbox-gh@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Any supported country</td><td>sandbox-{countryCode}@fonbnk.com</td><td>ZoA8dA9CXF</td></tr></tbody></table>

You can register your email, but orders will be automatically rejected.

Make sure to use the **Login with Password** flow:

<figure><img src="/files/Vet3e1FwiIBMRoDjq6wU" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Warning

During sandbox testing, do not use real money. Simply confirm the order, and it will be marked as paid.
{% endhint %}

If the correct **source** parameter is present in the URL, the order will be displayed in the **Orders** tab of the dashboard:

<figure><img src="/files/SCkoYgsxnt7YU1ifrlAW" alt=""><figcaption><p>Merchant dashboard on-ramp orders list</p></figcaption></figure>

Webhook requests will also be visible in the **Webhooks** tab of the dashboard:

<figure><img src="/files/nJwAESQwcfJpVw5jIwtN" alt=""><figcaption><p>Merchant dashboard on-ramp webhooks</p></figcaption></figure>

### Merchant API <a href="#merchant-api" id="merchant-api"></a>

For those who wish to access pay widget-related data from their back-end, our merchant API is available. You can find the API documentation here.

### Transitioning to Production <a href="#transitioning-to-production" id="transitioning-to-production"></a>

To create a live merchant account, proceed to register it here: <https://dashboard.fonbnk.com/register-initiate>. The live pay widget can be accessed at <https://pay.fonbnk.com/>.&#x20;

{% hint style="warning" %}
After registering, you'll need to contact our support team and complete a KYB process. Thereafter, you'll be able to receive webhooks and preconfigure user wallet addresses.
{% endhint %}


# On-ramp


# How it works

Fonbnk Pay Widget is a secure and efficient way to accept payments in stable coins from customers in an integrated and non-integrated way.

It's a P2P platform that connects people who want to sell mobile money (Airtime, Mobile Money, Bank, etc.) with people who would like to buy it.

How it works for end users:

1. A customer selects his funds source (Airtime, Mobile Money, Bank, etc.) and the amount of crypto he would like to receive
2. Customer provides his wallet details
3. Customer transfers funds to an agent we found for him and confirms the order
4. An agent confirms the order and the system sends crypto to a customer's wallet

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify the amount of crypto to buy
Widget->>User: Show the best offer
User->>Widget: Specify wallet details
User->>Widget: Verify email
User->>Widget: Create order
Widget->>User: Provide transfer funds instructions
Note over User: Send funds to an agent
User->>Widget: Confirm that funds are sent
Note over Agent: Check if funds are received
Agent->>Widget: Confirm that funds are received
Widget->>User: Send crypto to user wallet" %}

Example of a flow:

Pay Widget supports configuration via [URL parameters](/v1.5/on-ramp/url-parameters). Merchants can force the widget to use specific wallet address, memo, crypto amount, etc. This allows integrating the widget as a payment system.<br>


# URL Parameters

### On-ramp URL <a href="#off-ramp-url" id="off-ramp-url"></a>

| Environment | URL                                                                      |
| ----------- | ------------------------------------------------------------------------ |
| Sandbox     | [https://sandbox-pay.fonbnk.com](https://sandbox-pay.fonbnk.com/offramp) |
| Production  | [https://pay.fonbnk.com](https://pay.fonbnk.com/offramp)                 |

### List of parameters <a href="#list-of-parameters" id="list-of-parameters"></a>

Here is the list of parameters that can be added to the URL:

| Parameter       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| address         | <p>Address of the wallet you want to receive crypto to<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| network         | Wallet network. Supported values: ARBITRUM, AVALANCHE, BASE, BNB, CELO, ETHEREUM, LISK, OPTIMISM, POLYGON, SOLANA, STELLAR, TON, TRON                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| asset           | Wallet asset. Supported values: **USDC**, **CUSD**, **USDT**, **CKES, CGHS**, depending on the network. The default value is **USDC** for all networks that support it, except CELO, which is CUSD for CELO. Supported network/asset pairs: AVALANCHE (USDC/USDT), POLYGON (USDC, USDT), CELO (CUSD, USDC, USDT, CKES, CGHS), STELLAR (USDC), SOLANA (USDC, USDT),  BASE (USDC), ETHEREUM (USDC, USDT),  LISK (USDT), OPTIMISM (USDC, USDT), BNB (USDC, USDT), ARBITRUM (USDC, USDT), TRON (USDT), TON (USDT, USDE)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| memo            | Memo for the Stellar, TON and other networks transactions that support it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| amount          | If a currency is not provided, it will be an amount of crypto received after fees. If currency is **local,** it will be the amount of local currency a user should spend.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| currency        | Currency of the amount. Supported values: **local** or **crypto**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| countryIsoCode  | default selected country iso code, example: **KE** for Kenya, **NG** for Nigeria                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| currencyIsoCode | currency iso code, example: **KES** for Kenya, **NGN** for Nigeria. Acts like a country parameter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| freezeAmount    | Freezes the amount of order for the user, the user will not be able to change it. The amount is required in the URL for this parameter to work.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| freezeWallet    | <p>Freezes the wallet of order for the user, the user will not be able to change it. The wallet is required in the URL for this parameter to work.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| paymentChannel  | Default user funds source to select, supported values: **airtime**, **mobile\_money**, **bank**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| carrierCode     | The code of a mobile carrier to select by default. Examples: ng\_mtn, ke\_safaricom etc.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| callbackUrl     | <p>if present, "Back to website" link will be displayed on the success page. When a user clicks on it, we will redirect him to the provided URL. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p>                                                                                                                                                                                                                                                                                                                        |
| callbackBtnText | Text of the button that is displayed when **callbackUrl** is provided. Default is: "Back to website"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| email           | user's email                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| closeBtn        | text of the button that will be displayed on the success page. If not provided, the button will not be displayed. On click, it will send a *close-iframe* iframe event, so an integrator can close the widget.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| redirectUrl     | <p>if present, user will be redirected to this URL on order fail or success. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>, <strong><code>{status}</code></strong>, <strong><code>{failReason}</code></strong>. <strong><code>{status}</code></strong> placeholder can the next values: <strong><code>success</code></strong> or <strong><code>fail</code></strong>. Fail reason placeholder can the next values: <strong><code>transaction\_failure</code></strong> or <strong><code>agent\_rejected</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p> |
| quoteId         | id of a quote returned from the [price API request](https://docs.fonbnk.com/docs/pay-widget/merchant-api#get-expected-price).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| hideSwitch      | if present, hides the Buy/Sell switch at the top                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

Parameters allowed only for [registered merchants](https://docs.fonbnk.com/integration-guide#generating-payment-urls-and-creating-orders):

| Parameter   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| orderParams | This parameter will be sent to a merchant webhook after the success of the crypto transfer.                                                                                                                                                                                                                                                                                                                                                                        |
| source      | <p>parameter used to match an order to a merchant.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                          |
| signature   | <p>A JWT token (HS256 encryption algorithm) is generated using the "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow creating more than one order using the same signature.</p><p><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p> |

Here is an example of a URL with parameters:

{% code overflow="wrap" %}

```
https://pay.fonbnk.com?amount=1&network=POLYGON&asset=USDT
```

{% endcode %}

[<br>](https://docs.fonbnk.com/docs/pay-widget/integration-guide)


# Webhook

We can notify a [registered pay widget merchant](/v1.5/integration-guide) about the statuses of orders associated with him.

We will make a **POST** request to a provided webhook URL with the next **application/json** contents:

**Webhook V1:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-v1)

{% code overflow="wrap" %}

```typescript
type WebhookRequest = {
  "data": {
    "status":
      | "swap_initiated" // user has created an order
      | "swap_expired" // an order has expired
      | "swap_buyer_rejected"  // user has rejected an order
      | "swap_buyer_confirmed" // user has confirmed an order
      | "swap_seller_rejected" // agent has rejected an order, happens when agent don't receive a payment
      | "swap_seller_confirmed" // agent has confirmed an order
      | "pending" // USDC/cUSD transaction is pending
      | "complete" // USDC/cUSD transaction is complete
      | "failed", // USDC/cUSD transaction has failed
    "date": string, // date when event has happened
    "orderId": string, // order id in our system
    "email": string, // customer's email
    "localCurrencyAmount": number, // amount of local currency user paid
    "localCurrencyIsoCode": string, // ISO code of local currency user paid, e.g. KES, NGN etc.
    "countryIsoCode": string, // ISO code of country user paid from, e.g. KE, NG etc.
    "paymentChannel": // payment provider user paid with
      | "airtime"
      | "mobile_money"
      | "bank"
    "amount": number, // amount of USD user received
    "amountCrypto": number, // amount of crypto user received
    "network": // network user received USDC/cUSD on
      | "POLYGON"
      | "ETHEREUM"
      | "STELLAR"
      | "AVALANCHE"
      | "SOLANA"
      | "BASE"
      | "CELO"
      | "LISK",
    "asset": "USDC" | "CUSD" | "USDT" | "USDC_E", // asset user received
    "address": string, // address user received USDC/cUSD on
    "orderParams"?: string // Content of a orderParams query parameter provided to a pay widget URL. It might be useful for matching a merchant system user to an order user.
    "hash"?: string, // transaction hash
    "resumeUrl": string, // URL where user can resume his order, it point either to the transfer instructions page or to the status page
  },
  "hash": string, // SHA256 encrypted request.data string to validate a webhook request
};
```

{% endcode %}

**Webhook V2:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-v2)

Instead of sending hash inside - **WebhookRequest**, we will send it as a request **x-signature** header

```
Request headers:
x-signature: hash (string)
```

**Webhook verification:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-verification)

We send a hash field in our webhook to protect merchants from fraudulent requests. Each request should be verified by a secret provided in the dashboard.

Here is how it should be checked in pseudocode:

```
request.body.hash === SHA256(stringify(request.body.data), secret)
```

Here is how it should be checked in Node.js:

For Webhook V1 version:

```javascript
import { createHash } from 'crypto';

request.body.hash === createHash('sha256')
   .update(JSON.stringify(request.body.data))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

For Webhook V2 version:

```javascript
import { createHash } from 'crypto';

request 'x-signature' header === createHash('sha256')
   .update(JSON.stringify(request.body))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

{% hint style="info" %}
You can see how to make a signature in multiple programming languages [HERE](/v1.5/reference/signing-requests#request-examples)
{% endhint %}


# Skipping screens

### Typical flow

A typical user order follows these steps:

1. **Amount Selection**:\
   The user opens the "Amount" page, selects their country and preferred payment method, chooses a cryptocurrency, enters the desired amount, and clicks **"Next"**.
2. **Wallet Connection**:\
   The user is taken to the Wallet page, where they connect their wallet via MetaMask, WalletConnect, or another supported option. Once the wallet is successfully connected, the user is automatically redirected to the next step.
3. **Authentication**:\
   On the Auth page, the user enters their email address and submits a one-time password (OTP) sent to them.
   * If the order amount exceeds a certain threshold, the user is prompted to complete **KYC verification** by submitting ID document details, photos, and a selfie.
4. **Order Details**:\
   The user reviews the order details, fills in any required additional information (e.g., phone number, bank type), and clicks **"Transfer Funds"** to create the order. This redirects them to the next page.
5. **Transfer Instructions**:\
   The user receives instructions on how to transfer funds to the agent handling the order.\
   After sending the funds, the user clicks **"Confirm"**. Once the transaction is approved, the cryptocurrency is delivered to the user's wallet.

Most of the provided pages can be skipped, so a user journey would be much shorter.<br>

### Skipping the Amount page

To skip the Amount page, the next [URL parameters](/v1.5/on-ramp/url-parameters) must be predefined:

* countryIsoCode
* network
* asset
* currency
* amount
* source (from your merchant dashboard)

So, if you want to create an order for Nigeria via bank transfer for 2 CELO USDT, the URL will be the next:&#x20;

[https://pay.fonbnk.com/wallet?source=xsdf\_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG](https://pay.fonbnk.com/wallet?network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG)\
\
You've skipped the Amount page by opening the wallet page with preconfigured order params.

### Skipping the Wallet page

{% hint style="warning" %}
In production, you must be a verified merchant to generate signatures and predefine the wallet address
{% endhint %}

To skip the wallet page, we need to add 2 more parameters to the existing ones:

* address
* signature

"address" param contains the user's wallet address, and a signature is  a JWT token (HS256 encryption algorithm) generated using the "URL signature secret" value as a secret (from the merchant dashboard). You must add some unique value to the token payload to make each token unique because we don't allow to create more than 1 order using the same signature. During testing, you can generate a JWT signature using this website: <https://jwt.io/>. <br>

An example of a token generation in typescript:

```typescript
import * as jsonwebtoken from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';

const token = jsonwebtoken.sign(
    {
      uid: uuid(),
    },
    YOUR_SIGNATURE_SECRET,
    {
      algorithm: 'HS256',
    },
 );
```

So, if you want to skip the wallet page, the URL should look like this:\
[https://pay.fonbnk.com/auth?source=xsdf\_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA<br>](<https://pay.fonbnk.com/auth?source=xsdf_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA&#xA;>)\
You've skipped the Wallet page by opening the Auth page with preconfigured order params and wallet address with signature.

### Skipping the Auth page

To skip the auth page, you must log in on behalf of a user and provide his access and refresh tokens to the URL.

This step requires you to interact with our Merchant API. [Here](/v1.5/reference/signing-requests) you can find how to send requests.

You must call[ this endpoint](/v1.5/endpoints/user#post-api-user-tokens) with user email and country ISO code and in the response you'll get the access and refresh tokens.

```javascript
{​
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",​
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"​
​}
```

{% hint style="warning" %}
This type of API calls are disabled for merchants by default. Please contact our support team to enable this feature for your merchant.
{% endhint %}

After getting the tokens, you must add them to the URL as "at"  and "rt" params, so the URL would look like this:

[https://pay.fonbnk.com/swap?source=xsdf\_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA\&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9<br>](<https://pay.fonbnk.com/swap?source=xsdf_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA\&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&#xA;>)\
\
You've skipped the Auth page by opening the Order page with preconfigured order params, wallet address with signature, and already logged-in user via access and refresh token URL params.

### Skipping KYC

KYC step can be skipped only by removing the KYC requirement from the merchant, so we don't ask for KYC for this merchant's orders. This can be done by contacting our support team and only if you do KYC on your side already.

### Skipping the Order page

To skip the order page you must predefine 2 more URL parameters:

* quoteId
* requiredFields

Both of these params you get from the [best offer API endpoint](/v1.5/endpoints/on-ramp#get-api-onramp-best-offer) with includeRequiredFields param present. The response will be like this:<br>

```json
{
  "quoteId": "6878df150d6289ffdedcd6f4",
  ...,
  "requiredFields": {
    "phoneNumber": {
      "label": "Your phone number",
      "sellerLabel": "Buyer's phone number",
      "type": "phone",
      "required": true
    }
  }
}
```

Now you must fill a required field object, stringify it and encode for URL safety and provide quoteId and requiredFields to the URL.\
\
Required fields encoding example:

```typescript
const values = {
  phoneNumber: "2346034088631"
};
const encoded = encodeURIComponent(JSON.stringify(values));
// %7B%22phoneNumber%22%3A%222346034088631%22%7D
```

Now, you must add all the parameters to the /auto-order page, aslo the flow=onramp must be added because this page can be used for off-ramps too:

\
[https://pay.fonbnk.com/auto-order?source=xsdf\_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA\&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&quoteId=6878df150d6289ffdedcd6f4\&requiredFields=%7B%22phoneNumber%22%3A%222346034088631%22%7D\&flow=onramp<br>](<https://pay.fonbnk.com/auto-order?source=xsdf_2\&network=CELO\&asset=USDT\&amount=2\&currency=crypto\&paymentChannel=bank\&countryIsoCode=NG\&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f\&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA\&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&quoteId=6878df150d6289ffdedcd6f4\&requiredFields=%7B%22phoneNumber%22%3A%222346034088631%22%7D\&flow=onramp&#xA;>)\
\
When a user lands on this page, an order will be created automatically, and a user will see the transfer instructions page.<br>

### Recommended way

You can use /auto-order page for all the cases above; just provide as much info as you can to this page and it'll automatically redirect to the appropriate page, don't forget to add the flow=onramp param there.


# Server to server integration

{% hint style="warning" %}
Full server to server integration is possible only after a merchant KYB process
{% endhint %}

It's possible to do a full on-ramp flow by using only merchant API. [Here](/v1.5/reference/signing-requests) you can find how to send API requests correctly.\
\
The full workflow should look like this:

1. [Get a list of supported countries](/v1.5/endpoints/on-ramp#get-api-onramp-payment-channels) and their payment channels.
2. [Get a list of supported blockchain assets](/v1.5/endpoints/util#get-api-util-assets).
3. Pick a country, payment channel, and blockchain asset. [Get order limits](/v1.5/endpoints/on-ramp#get-api-onramp-limits) using these values.
4. [Get the user's KYC status](/v1.5/endpoints/user#post-api-user-kyc-status) and check if they need to pass a KYC process. If they need a KYC, [submit the document information](/v1.5/endpoints/user#post-api-user-kyc-submit) and [check the KYC status](/v1.5/endpoints/user#post-api-user-kyc-status) until it's accepted.
5. [Get the best offer](/v1.5/endpoints/on-ramp#get-api-onramp-best-offer) , to get the quoteId, understand how much a user should pay, and what additional information is required from a user to create an order.
6. [Create an order](/v1.5/endpoints/on-ramp#post-api-onramp-order-create) using the user's email, desired amount, blockchain asset, country, payment channel, additional data required from a user, and quoteId. Some orders may require [verifying an OTP code sent to a user](/v1.5/endpoints/on-ramp#post-api-onramp-order-otp).
7. [Confirm that a user sent funds to an agent](/v1.5/endpoints/on-ramp#post-api-onramp-order-confirm).

### Getting countries, payment channels, blockchain assets and order limits

Let's [get a list of supported countries](/v1.5/endpoints/on-ramp#get-api-onramp-payment-channels) , the response would be like this:

```json
[
  {
    "name": "Nigeria",
    "countryIsoCode": "NG",
    "currencyIsoCode": "NGN",
    "paymentChannels": [
      {
        "paymentChannel": "bank",
        "description": "Bank transfer",
        "requiresCarrier": false,
        "carriers": []
      },
      {
        "paymentChannel": "airtime",
        "description": "Airtime",
        "requiresCarrier": true,
        "carriers": [
          {
            "id": "618e43914f57e07d255ff357",
            "name": "Airtel Nigeria"
          },
          ...
        ]
      }
    ]
  },
  ...
]
```

We see that Nigeria is supported for on-ramp and has payment channels: bank and airtime.

Let's pick this country and bank payment channel.\
\
Let's [get a list of supported blockchain assets](/v1.5/endpoints/util#get-api-util-assets):&#x20;

```json
[
  {
    "network": "POLYGON",
    "asset": "USDC",
    "canOfframp": true,
    "canOnramp": true
  },
  {
    "network": "ETHEREUM",
    "asset": "USDC",
    "canOfframp": true,
    "canOnramp": true
  },
  ...
]
```

We see that POLYGON USDC is supported for on-ramp; let's pick it.

Now, let's [check order limits](/v1.5/endpoints/on-ramp#get-api-onramp-limits) for Nigeria, bank payment channel and POLYGON USDC asset.\
Request query params:

```
?countryIsoCode=NG&paymentChannel=bank&network=POLYGON&asset=USDC
```

Response:

```json
{
  "minUsd": 1,
  "maxUsd": 100,
  "minLocalCurrency": 1534,
  "maxLocalCurrency": 153376,
  "minCrypto": 1,
  "maxCrypto": 100
}

```

So, now we understand that a user can buy from 1 to 100 POLYGON USDC and can pay from 1534 to 153376 NGN.

### KYC

Let's [check a user's KYC status](/v1.5/endpoints/user#post-api-user-kyc-status) to determine if we need to sumbit a KYC documents.\
Request body:

```json
{
    email: "example@mail.com",
    countryIsoCode: "NG"
}
```

Response:

<pre class="language-json"><code class="lang-json">{
  "reachedKycLimit": false,
<strong>  "basicDocuments": [
</strong>    {
      "_id": "67da909b739fc481aa525c45",
      "type": "basic",
      "title": "BVN",
      "value": "BVN",
      "requiredFields": {
        "first_name": {
          "type": "string",
          "label": "First Name",
          "required": true
        },
        "last_name": {
          "type": "string",
          "label": "Last Name",
          "required": true
        },
        "dob": {
          "type": "date",
          "label": "Date of birth",
          "required": true
        },
        "email": {
          "type": "email",
          "label": "Email",
          "required": true
        },
        "id_number": {
          "type": "string",
          "label": "BVN Number",
          "required": true,
          "format": "00000000000",
          "regexp": "^[0-9]{11}$"
        }
      }
    },
<strong>    ...
</strong>  ],
  "advancedDocuments": [
    {
      "_id": "67da93c0dfd3a00f3380b857",
      "type": "advanced",
      "title": "Driving License",
      "value": "DRIVERS_LICENSE",
      "requiredFields": {
        "first_name": {
          "type": "string",
          "label": "First Name",
          "required": true
        },
        "last_name": {
          "type": "string",
          "label": "Last Name",
          "required": true
        },
        "dob": {
          "type": "date",
          "label": "Date of birth",
          "required": true
        },
        "email": {
          "type": "email",
          "label": "Email",
          "required": true
        },
        "images": {
          "type": "smile-identity-images",
          "label": "Verification images",
          "required": true
        }
      }
    },
    ...
  ],
  "kycRules": {
    "onramp": [
      { min: 0, max: 10, type: 'none' },
      { min: 10, max: 50, type: 'basic' },
      { min: 50, max: 100, type: 'advanced' },
    ],
    "offramp": [
      { min: 0, max: 7, type: 'none' },
      { min: 7, max: 35, type: 'basic' },
      { min: 35, max: 100, type: 'advanced' },
    ],
  },
}

</code></pre>

We see that there's no passedKycType field which means that the user haven't completed a KYC process in our system. Moreover, we see a list of supported documents for basic and advanced KYC.&#x20;

The **kycRules** field indicates that we don't need a KYC for orders below $10, need a basic KYC for the $10-50(not including) range, and need an advanced KYC for the $50-100 range.

For demonstration purposes, let's at first complete the basic KYC and then the advanced one.\
Let's pick a basic document to submit:

```json
{
      "_id": "67da909b739fc481aa525c45",
      "type": "basic",
      "title": "BVN",
      "value": "BVN",
      "requiredFields": {
        "first_name": {
          "type": "string",
          "label": "First Name",
          "required": true
        },
        "last_name": {
          "type": "string",
          "label": "Last Name",
          "required": true
        },
        "dob": {
          "type": "date",
          "label": "Date of birth",
          "required": true
        },
        "email": {
          "type": "email",
          "label": "Email",
          "required": true
        },
        "id_number": {
          "type": "string",
          "label": "BVN Number",
          "required": true,
          "format": "00000000000",
          "regexp": "^[0-9]{11}$"
        }
      }
    }
```

We need to build an object with keys described under "requiredFields", like this:

```json
{
    "first_name": "Joe",
    "last_name": "Doe",
    "dob": "2000-01-01T00:00:00.000Z",
    "email": "example@mail.com",
    "id_number": "00000000000"
}
```

[Then we submit it](/v1.5/endpoints/user#post-api-user-kyc-submit) using the user's email, countryIsoCode, document ID, and these fields.<br>

Request body:

```json
{
      "email": "example@mail.com",
      "countryIsoCode": "NG",
      "documentId": "67da909b739fc481aa525c45",
      "userFields": {
        "first_name": "Joe",
        "last_name": "Doe",
        "dob": "2000-01-01T00:00:00.000Z",
        "email": "example@mail.com",
        "id_number": "00000000012",
      },
}

```

Let's [check a user's KYC status](/v1.5/endpoints/user#post-api-user-kyc-status) again:

```json
{
    "passedKycType": "basic",
    "kycStatus": "approved",
    "kycStatusDescription": "Partial Match",
    ...
}
```

We see that a user passed the KYC and now has passedKycType = basic. If KYC check was still pending the response would be like the following:

```json
{
    "kycStatus": "initiated",
}
```

Failed KYC would look like the following:

```json
{
    "kycStatus": "rejected",
    "kycStatusDescription": "Unable to verify ID - Result Not Found",
}
```

In case of rejected KYC you can try to submit a new one untill  reachedKycLimit = true, thereafter, you need to contact our support team.

Now pick a document for an advanced KYC :

```json
{
      "_id": "67da93c0dfd3a00f3380b857",
      "type": "advanced",
      "title": "Driving License",
      "value": "DRIVERS_LICENSE",
      "requiredFields": {
        "first_name": {
          "type": "string",
          "label": "First Name",
          "required": true
        },
        "last_name": {
          "type": "string",
          "label": "Last Name",
          "required": true
        },
        "dob": {
          "type": "date",
          "label": "Date of birth",
          "required": true
        },
        "email": {
          "type": "email",
          "label": "Email",
          "required": true
        },
        "images": {
          "type": "smile-identity-images",
          "label": "Verification images",
          "required": true
        }
      }
    }
```

Everything is the same except the "images" field. It requires you to submit a photos of both sides of user's document and a user's selfie. Let's imagine that you took these photos and uploaded to the file storage under these URLs: <https://cdn.com/selfie.jpg>, <https://cdn.com/front.jpg>, <https://cdn.com/back.jpg>, the request to submit the KYC would look like the following:

```json
{
      "email": "example@mail.com",
      "countryIsoCode": "NG",
      "documentId": "67da909b739fc481aa525c45",
      "userFields": {
        "first_name": "Joe",
        "last_name": "Doe",
        "dob": "2000-01-01T00:00:00.000Z",
        "email": "example@mail.com",
        "images": [
          {
            "image_type_id": 0,
            "image": "https://cdn.com/selfie.jpg" 
          }, 
          {
            "image_type_id": 1,
            "image": "https://cdn.com/front.jpg" 
          }, 
          {
            "image_type_id": 5,
            "image": "https://cdn.com/back.jpg" 
          }
        ]
      },
}

```

The rest of the logic is the same

### Creating an order

Let's get [the best offer](/v1.5/endpoints/on-ramp#get-api-onramp-best-offer) using parameters picked in previous steps, the order will be for 5 POLYGON USDC.

Request query:

<pre data-overflow="wrap"><code><strong>?countryIsoCode=NG&#x26;paymentChannel=bank&#x26;network=POLYGON&#x26;asset=USDC&#x26;amount=5&#x26;currency=crypto&#x26;includeRequiredFields=true
</strong></code></pre>

Response:

```json
{
  "quoteId": "687a45186848159c27269e38",
  "offer": {
    "countryIsoCode": "NG",
    "currencyIsoCode": "NGN",
    "paymentChannel": "bank",
    "exchangeRate": 1532.89,
    "cryptoExchangeRate": 1532.89
  },
  "cashout": {
    "localCurrencyAmount": 7664,
    "totalAmountUsd": 5,
    "totalAmountCrypto": 5,
    "withdrawAmountUsd": 5,
    "withdrawAmountCrypto": 5,
    "feePercent": 0,
    "feeAmountUsd": 0,
    "feeAmountLocalCurrency": 0,
    "feeAmountCrypto": 0,
    "feePercentFonbnk": 0,
    "feeAmountUsdFonbnk": 0,
    "feeAmountLocalCurrencyFonbnk": 0,
    "feeAmountCryptoFonbnk": 0,
    "feePercentPartner": 0,
    "feeAmountUsdPartner": 0,
    "feeAmountLocalCurrencyPartner": 0,
    "feeAmountCryptoPartner": 0,
    "gasAmountUsd": 0.00112894,
    "gasAmountCrypto": 0,
    "gasAmountLocalCurrency": 2
  },
  "requiredFields": {
    "buyerFirstName": {
      "label": "Your bank account first name",
      "type": "string",
      "required": true,
      "sellerLabel": "Buyer first name"
    },
    "buyerLastName": {
      "label": "Your bank account last name",
      "type": "string",
      "required": true,
      "sellerLabel": "Buyer last name"
    },
    "buyerEmail": {
      "label": "Your email",
      "type": "email",
      "required": true,
      "sellerLabel": "Buyer email"
    },
    "bankCode": {
      "required": true,
      "type": "enum",
      "label": "Bank name",
      "sellerLabel": "Bank name",
      "options": [
        {
          "value": "120001",
          "label": "9mobile 9Payment Service Bank"
        },
        {
          "value": "50871",
          "label": "Unical MFB"
        },
        ...
      ]
    },
    "phoneNumber": {
      "label": "Your phone number",
      "sellerLabel": "Buyer's phone number",
      "type": "phone",
      "required": true
    }
  }
}
```

From the response, we understand that a user should pay 7664 NGN to receive 5 POLYGON USDC, also we see that the next additional data is required from a user: buyerFirstName, buyerLastName,  buyerEmail, bankCode, phoneNumber.&#x20;

Let's [create an order](/v1.5/endpoints/on-ramp#post-api-onramp-order-create) using all this data.

Request body:

```json
{
  "quoteId": "687a45186848159c27269e38",
  "email": "example@mail.com",
  "network": "POLYGON",
  "asset": "USDC",
  "amount": 5,
  "currency": "crypto",
  "address": "0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f",
  "userIp": "145.234.234.55",
  "redirectUrl": "https://your-website.con/fonbnk-success-page",
  "extraFields": {
    "buyerFirstName": "John",
    "buyerLastName": "Doe",
    "buyerEmail": "example@mail.com",
    "bankCode": "120001",
    "phoneNumber": "234567890123",
  },
}
```

Response:

```json
{
  "_id": "687a48eab6d730f80856e1ca",
  "status": "swap_initiated",
  "date": "2025-07-18T13:15:23.289Z",
  "orderId": "687a48eab6d730f80856e1ca",
  "email": "example@mail.com",
  "localCurrencyAmount": 7664,
  "currencyIsoCode": "NGN",
  "countryIsoCode": "NG",
  "paymentChannel": "bank",
  "amount": 5,
  "amountCrypto": 5,
  "network": "POLYGON",
  "asset": "USDC",
  "address": "0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f",
  "memo": null,
  "orderParams": null,
  "resumeUrl": "https://sandbox-pay.fonbnk.com/ussd/687a48eab6d730f80856e1ca",
  "carrierId": "618e43914f57e07d255ff351",
  "feePercent": 0,
  "feePercentFonbnk": 0,
  "feePercentPartner": 0,
  "feeAmountUsd": 0,
  "feeAmountLocalCurrency": 0,
  "feeAmountUsdFonbnk": 0,
  "feeAmountLocalCurrencyFonbnk": 0,
  "feeAmountUsdPartner": 0,
  "feeAmountLocalCurrencyPartner": 0,
  "gasAmountUsd": 0.00112894,
  "transferInstructions": {
    "type": "manual",
    "instructionsText": "It is a sandbox offer. If you are using test account, please confirm the transfer from your side and seller will automatically confirm the transfer from his side within 1 minute.",
    "warningText": "Created orders from non-test accounts will be automatically canceled after 5 minutes.",
    "transferDetails": {
      "bankAccountNumber": {
        "label": "Agent's bank account number",
        "value": "5158809613"
      },
      "bankName": {
        "label": "Agent's bank name",
        "value": "Fidelity Bank"
      },
      "bankAccountHolderName": {
        "label": "Agent's bank account holder name",
        "value": "SANDY BOXERRITTO"
      },
      "narration": {
        "label": "Narration",
        "description": "Transfer without narration will be ignored by the system.",
        "value": "6JTQAC"
      }
    }
  },
  "gasAmountLocalCurrency": 1
}
```

Order is created, now a user must pay to the agent using the "transferInstructions" details.

Here are possible types of transfer instructions:

**Manual**, a user pays manually to the provided details:

```json
{
  "transferInstructions": {
    "type": "manual",
    "transferDetails": {
      "bankAccountNumber": {
        "label": "Agent's bank account number",
        "value": "9637959770"
      },
      "bankName": {
        "label": "Agent's bank name",
        "value": "PROVIDUS BANK"
      },
      "bankAccountHolderName": {
        "label": "Agent's bank account holder name",
        "value": "Start Button Limited(Checkout)"
      },
      "narration": {
        "label": "Narration",
        "description": "TRANSFER WITHOUT NARRATION WILL BE IGNORED BY THE SYSTEM.",
        "value": "shc-0x6on2w5js"
      }
    },
    "instructionsText": "Transfer the NGN to the agent's bank account.",
    "warningText": "Important: Only transfer funds from a bank account you specified previously. Send the exact NGN amount. Use the displayed account for this transaction only."
  }
}
```

**STK Push**, user receives a mobile carrier popup and confirms a transfer:

```json
{
  "transferInstructions": {
    "type": "stk_push",
    "instructionsText": "You’ll be prompted with a USSD dialog to proceed the transfer. If the transfer is unsuccessful or you don’t receive the USSD dialog, please retry the transfer"
  }
}
```

**Redirect**, user must open a specidied redirect URL and complete the transfer there. After the success they will be redirected to the URL specified during order creation.

```json
{
  "transferInstructions": {
    "type": "redirect",
    "instructionsText": "You’ll be redirected to Flutterwave checkout. Enter the OTP code received to initiate the transaction. If you have any issues, please retry the transfer",
    "paymentUrl": "https://checkout.flutterwave.com/captcha/verify/lang-en/9704816:565eee314972431349bd77403e57a741"
  }
}

```

**USSD**, in that case, a user must execute the provided USSD code to complete the transfer. USSD code may include a {pin} placeholder; in that case you must ask the user to provide a pin code and replace the placeholder with it before execution.

```json
{
  "transferInstructions": {
    "type": "ussd",
    "ussdCode": "*321*0701232567*1587*{pin}#",
    "transferDetails": {
      "phoneNumber": {
        "label": "Agent's phone number",
        "value": "254701232567"
      }
    },
    "instructionsText": "Dial the USSD code and follow the instructions to complete the transfer. You have to replace {pin} placeholder with your PIN code if you dial USSD code manually.",
    "warningText": "Important: Transfer airtime NGN to the agent from the phone number you specified during order creation."
  }
}

```

**STK Push with OTP**, in that case user receives an sms with an OTP code which must be sent to the [confirm OTP endpoint](/v1.5/endpoints/on-ramp#post-api-onramp-order-otp) , after that a user receives the STK Push and pays for the order

```json
{
  "transferInstructions": {
    "type": "otp_stk_push",
    "transferDetails": {},
    "isOtpRequired": true,
    "instructionsText": "Enter the OTP code received to initiate the transaction and you’ll be prompted with a USSD dialog to proceed the transfer. If the transfer is unsuccessful or you don’t receive the USSD dialog, please retry the transfer",
    "actionButtonText": "Verify OTP code"
  }
}
```

After a user sends funds to the agent, you must [confirm the order](/v1.5/endpoints/on-ramp#post-api-onramp-order-confirm).

Request body:

```json
{
  "orderId": "687a48eab6d730f80856e1ca"
}
```

The order flow is finished; now you need to wait for crypto to be sent to the user's wallet. You can do that either by waiting for a [webhook](/v1.5/on-ramp/webhook) or by [fetching the order](/v1.5/endpoints/on-ramp#get-api-onramp-order).


# Off-ramp


# How it works

Off-ramp widget allows user to exchange their crypto currency to his country's local currency.

How it works for end users:

1. Customer selects off-ramp type and specifies the amount of crypto he wants to exchange. System displays how much local currency he will receive.
2. Customer verifies his email by entering a code sent to him.
3. Customer provides his account details such as bank account number, bank name, etc.
4. System returns a wallet address where customer should send his crypto.
5. Customer sends crypto to the provided address and provides a transaction hash to the system.
6. System checks if the transaction is received and sends local currency to the customer's account.

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify amount of crypto to exchange
Widget->>User: Show the best offer
User->>Widget: Verify email
User->>Widget: Provide account details
User->>Widget: Create order
Widget->>User: Wallet address to send crypto
Note over User: Send crypto to the wallet
User->>Widget: Send transaction hash
Note over Widget: Check if funds are received
Widget->>User: Send local currency to the user account" %}


# URL Parameters

### Off-ramp URL[​](https://docs.fonbnk.com/docs/offramp/query-params#off-ramp-url) <a href="#off-ramp-url" id="off-ramp-url"></a>

| Environment | URL                                      |
| ----------- | ---------------------------------------- |
| Sandbox     | <https://sandbox-pay.fonbnk.com/offramp> |
| Production  | <https://pay.fonbnk.com/offramp>         |

### List of parameters[​](https://docs.fonbnk.com/docs/offramp/query-params#list-of-parameters) <a href="#list-of-parameters" id="list-of-parameters"></a>

Here is the list of parameters that can be added to the URL:

| Parameter       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| network         | Wallet network from which user will send crypto. Supported values: ARBITRUM, AVALANCHE, BASE, BNB, CELO, ETHEREUM, LISK, OPTIMISM, POLYGON, SOLANA, STELLAR, TON, TRON                                                                                                                                                                                                                                                                                                                                                                                  |
| asset           | Wallet asset from which the user will send crypto.Supported values: **USDC**, **CUSD**, **USDT**, **CKES, CGHS**, depending on the network. The default value is **USDC** for all networks that support it, except CELO, which is CUSD for CELO. Supported network/asset pairs: AVALANCHE (USDC/USDT), POLYGON (USDC, USDT), CELO (CUSD, USDC, USDT, CKES, CGHS), STELLAR (USDC), SOLANA (USDC, USDT),  BASE (USDC), ETHEREUM (USDC, USDT),  LISK (USDT), OPTIMISM (USDC, USDT), BNB (USDC, USDT), ARBITRUM (USDC, USDT), TRON (USDT), TON (USDT, USDE) |
| amount          | If a currency is not provided, it will be an amount of crypto user wants to exchange. If currency is **local** it will be the amount of local currency user wants to receive.                                                                                                                                                                                                                                                                                                                                                                           |
| currency        | Currency of the amount. Supported values: **crypto** or **local**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| paymentChannel  | Type of the off-ramp: **bank**, **airtime**, **mobile\_money**, **paybill**                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| countryIsoCode  | default selected country iso code, example: **KE** for Kenya, **NG** for Nigeria                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| currencyIsoCode | currency iso code, example: **KES** for Kenya, **NGN** for Nigeria. Acts like a country parameter.                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| freezeAmount    | Freezes the amount of order for the user, the user will not be able to change it. The amount is required in the URL for this parameter to work.                                                                                                                                                                                                                                                                                                                                                                                                         |
| freezeWallet    | Freezes the wallet of order for the user, the user will not be able to change it. The wallet is required in the URL for this parameter to work.                                                                                                                                                                                                                                                                                                                                                                                                         |
| hideSwitch      | if present, hides Buy/Sell switch at the top                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

Parameters allowed only for [registered merchants](https://docs.fonbnk.com/integration-guide#generating-payment-urls-and-creating-orders):&#x20;

| Parameter   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| orderParams | This parameter will be sent to a merchant webhook with order status changes                                                                                                                                                                                                                                                                                                                                                                                        |
| source      | <p>parameter used to match an order to a merchant</p><p><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                        |
| signature   | <p>A JWT token (HS256 encryption algorithm) is generated using the "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow creating more than one order using the same signature.</p><p><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p> |

Here is an example of a URL with parameters:

{% code overflow="wrap" %}

```
https://pay.fonbnk.com/offramp?amount=1&network=POLYGON&asset=USDT
```

{% endcode %}


# Webhook

We can notify a merchant about the statuses of off-ramp orders associated with him.

We will make a **POST** request to a provided webhook URL with the next **application/json** contents:

**Webhook V1:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-v1)

<pre class="language-typescript"><code class="lang-typescript">type WebhookRequest = {
  data: {
    orderId: string,
    paymentChannel: "bank",
    status: OfframpStatus,
    date: string,
    cashout: {
      localCurrencyAmount: number, // how much the user will receive in local currency
      usdAmount: number, // how much user must send in USD
      feeAmountUsd: number, // total fee amount in USD
      feeAmountUsdFonbnk: number, // fee amount in USD for Fonbnk
      feeAmountUsdPartner: number, // fee amount in USD for partner
      feeAmountLocalCurrency: number, // total fee amount in local currency
      feeAmountLocalCurrencyFonbnk: number, // fee amount in local currency for Fonbnk
      feeAmountLocalCurrencyPartner: number, // fee amount in local currency for partner
    },
    exchangeRate: number,
    network: "AVALANCHE" | "POLYGON" | "CELO",
    asset: "USDC" | "CUSD",
    fromAddress: string,
    toAddress: string,
    userEmail: string,
    requiredFields: { label: string, type: 'number' | 'string' | 'date' | 'boolean' | 'email' | 'phone', value: string }[],// user account data
    orderParams?: string, // contents of orderParams query parameter during order creation
    countryIsoCode: string,
    currencyIsoCode: string,
  },
  hash: string,
};

enum OfframpStatus  {
  INITIATED = 'initiated', 
  VALIDATING_TRANSACTION = 'validating_transaction', // user has sent us a transaction hash, waiting it to appear in a blockchain
  TRANSACTION_INVALID = 'transaction_invalid', // submited transaction hash is invalid (wrong amount, wrong creation time etc.)
  AWAITING_TRANSACTION_CONFIRMATION = 'awaiting_transaction_confirmation', //waiting for transaction confirmation
  TRANSACTION_CONFIRMED = 'transaction_confirmed', // user transaction was confirmed
  TRANSACTION_FAILED = 'transaction_failed', // user transaction is not confirmed in the blockchain
  OFFRAMP_SUCCESS = 'offramp_success',  // user has received the funds
<strong>  OFFRAMP_RETRY = "offramp_retry", // we are retrying the off-ramp after a failed attempt
</strong>  TRANSACTION_FAILED = 'transaction_failed', // user transaction failed
  OFFRAMP_PENDING = 'offramp_pending', // offramp in progress
  OFFRAMP_FAILED = 'offramp_failed', // offramp failed
  REFUNDING = 'refunding', // offramp failed, refund in progress
  REFUNDED = 'refunded', // offramp failed, refund was successful
  REFUND_FAILED = 'refund_failed', // offramp failed, refund failed
  EXPIRED = 'expired', // user did not send us a transaction hash in time
  CANCELLED = "cancelled" // user cancelled an order
}
</code></pre>

**Webhook V2:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-v2)

Instead of sending hash inside - **WebhookRequest**, we will send it as a request **x-signature** header

```
Request headers:
x-signature: hash (string)
```

**Webhook verification:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-verification)

We send a hash field in our webhook to protect merchants from fraudulent requests. Each request should be verified by a secret provided in the dashboard.

Here is how it should be checked in pseudocode:

```
request.body.hash === SHA256(stringify(request.body.data), secret)
```

Here is how it should be checked in Node.js:

For Webhook V1 version:

```javascript
import { createHash } from 'crypto';

request.body.hash === createHash('sha256')
   .update(JSON.stringify(request.body.data))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

For Webhook V2 version:

```javascript
import { createHash } from 'crypto';

request 'x-signature' header === createHash('sha256')
   .update(JSON.stringify(request.body))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

{% hint style="info" %}
You can see how to make a signature in multiple programming languages [HERE](/v1.5/reference/signing-requests#request-examples)
{% endhint %}


# Servers

### API servers[​](https://docs.fonbnk.com/docs/offramp/merchant-api#api-servers) <a href="#api-servers" id="api-servers"></a>

| Environment | Server URL                                                        |
| ----------- | ----------------------------------------------------------------- |
| Sandbox     | [https://sandbox-api.fonbnk.com](https://sandbox-api.fonbnk.com/) |
| Production  | [https://api.fonbnk.com](https://api.fonbnk.com/)                 |


# Signing requests

### Request Authentication[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#request-authentication) <a href="#request-authentication" id="request-authentication"></a>

All requests should be signed using a HMAC256 algorithm and provided `clientId` and `clientSecret`.

### How to get the signature of the request?[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#how-to-get-the-signature-of-the-request) <a href="#how-to-get-the-signature-of-the-request" id="how-to-get-the-signature-of-the-request"></a>

1. Generate a timestamp (Epoch Unix Timestamp) in milliseconds
2. Concatenate the timestamp and the endpoint that is called `{timestamp}:{endpoint}`
3. Decode the base64 encoded clientSecret
4. Compute the SHA256 hash of the concatenated string. Use decoded clientSecret as a key. Convert the result to base64
5. Add the clientId, signature, and timestamp to HTTP headers

The following pseudocode example demonstrates and explains how to sign a request

{% code overflow="wrap" %}

```
timestamp = CurrentTimestamp();
stringToSign = timestamp + ":" + endpoint;
signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( clientSecret ), UTF8 ( concatenatedString ) ) );
```

{% endcode %}

## Request examples <a href="#request-examples" id="request-examples"></a>

The following examples send HTTP request to [get best on-ramp offer](https://docs.fonbnk.com/endpoints/on-ramp#get-api-onramp-best-offer) API endpoint:

{% tabs %}
{% tab title="Typescript" %}
{% code overflow="wrap" %}

```typescript
import crypto from 'crypto';
const BASE_URL = 'https://api.fonbnk.com';
const ENDPOINT = '/api/onramp/best-offer';
const CLIENT_ID = '';
const CLIENT_SECRET = '';

const generateSignature = ({
  clientSecret,
  timestamp,
  endpoint,
}: {
  clientSecret: string;
  timestamp: string;
  endpoint: string;
}) => {
  let hmac = crypto.createHmac('sha256', Buffer.from(clientSecret, 'base64'));
  let stringToSign = `${timestamp}:${endpoint}`;
  hmac.update(stringToSign);
  return hmac.digest('base64');
};

const main = async () => {
  const timestamp = new Date().getTime();
  const queryParams = new URLSearchParams({
    countryIsoCode: 'NG',
    amount: '10',
    currency: 'crypto',
    network: 'CELO',
    asset: 'CUSD',
    paymentChannel: 'bank',
  });
  const endpoint = `${ENDPOINT}?${queryParams.toString()}`;
  const signature = generateSignature({
    clientSecret: CLIENT_SECRET,
    timestamp: timestamp.toString(),
    endpoint,
  });
  const headers = {
    'Content-Type': 'application/json',
    'x-client-id': CLIENT_ID,
    'x-timestamp': timestamp.toString(),
    'x-signature': signature,
  };
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    method: 'GET',
    headers,
  });
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
};

main().catch(console.error);

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```python
import hmac
import base64
import time
import requests
from urllib.parse import urlencode

BASE_URL = 'https://api.fonbnk.com'
ENDPOINT = '/api/onramp/best-offer'
CLIENT_ID = ''
CLIENT_SECRET = ''

def pad_base64(base64_string):
    return base64_string + '=' * (-len(base64_string) % 4)

def generate_signature(client_secret, timestamp, endpoint):
    client_secret_padded = pad_base64(client_secret)
    hmac_obj = hmac.new(base64.b64decode(client_secret_padded), f'{timestamp}:{endpoint}'.encode('utf-8'), 'sha256')
    return base64.b64encode(hmac_obj.digest()).decode('utf-8')

def main():
    timestamp = str(int(time.time() * 1000))
    query_params = {
        'countryIsoCode': 'NG',
        'amount': '10',
        'currency': 'crypto',
        'network': 'CELO',
        'asset': 'CUSD',
        'paymentChannel': 'bank',
    }
    endpoint = f"{ENDPOINT}?{urlencode(query_params)}"
    signature = generate_signature(CLIENT_SECRET, timestamp, endpoint)
    headers = {
        'Content-Type': 'application/json',
        'x-client-id': CLIENT_ID,
        'x-timestamp': timestamp,
        'x-signature': signature,
    }
    response = requests.get(f"{BASE_URL}{endpoint}", headers=headers)
    data = response.json()
    print(data)

if __name__ == "__main__":
    main()

```

{% endcode %}
{% endtab %}

{% tab title="GO" %}
{% code overflow="wrap" %}

```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	BASE_URL      = "https://api.fonbnk.com"
	ENDPOINT      = "/api/onramp/price"
	CLIENT_ID     = ""
	CLIENT_SECRET = ""
)

func padBase64(base64String string) string {
	return base64String + strings.Repeat("=", (4-len(base64String)%4)%4)
}

func generateSignature(clientSecret, timestamp, endpoint string) (string, error) {
	clientSecretPadded := padBase64(clientSecret)
	decodedSecret, err := base64.StdEncoding.DecodeString(clientSecretPadded)
	if err != nil {
		return "", err
	}
	message := fmt.Sprintf("%s:%s", timestamp, endpoint)
	h := hmac.New(sha256.New, decodedSecret)
	h.Write([]byte(message))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

func main() {
	timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
	queryParams := url.Values{
		"countryIsoCode": {"NG"},
		"amount":         {"10"},
		"currency":       {"crypto"},
		"network":        {"CELO"},
		"asset":          {"CUSD"},
		"paymentChannel": {"bank"},
	}
	endpoint := fmt.Sprintf("%s?%s", ENDPOINT, queryParams.Encode())
	signature, err := generateSignature(CLIENT_SECRET, timestamp, endpoint)
	if err != nil {
		fmt.Println("Error generating signature:", err)
		return
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", BASE_URL+endpoint, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-client-id", CLIENT_ID)
	req.Header.Set("x-timestamp", timestamp)
	req.Header.Set("x-signature", signature)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making request:", err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	var data map[string]interface{}
	if err := json.Unmarshal(body, &data); err != nil {
		fmt.Println("Error unmarshalling response:", err)
		return
	}

	fmt.Println(data)
}

```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code overflow="wrap" %}

```php
<?php

define('BASE_URL', 'https://api.fonbnk.com');
define('ENDPOINT', '/api/onramp/best-offer');
define('CLIENT_ID', '');
define('CLIENT_SECRET', '');

function pad_base64($base64_string) {
    return $base64_string . str_repeat('=', (4 - strlen($base64_string) % 4) % 4);
}

function generate_signature($client_secret, $timestamp, $endpoint) {
    $client_secret_padded = pad_base64($client_secret);
    $hmac = hash_hmac('sha256', "$timestamp:$endpoint", base64_decode($client_secret_padded), true);
    return base64_encode($hmac);
}

function main() {
    $timestamp = (string) round(microtime(true) * 1000);
    $query_params = [
        'countryIsoCode' => 'NG',
        'amount' => '10',
        'currency' => 'crypto',
        'network' => 'CELO',
        'asset' => 'CUSD',
        'paymentChannel' => 'bank',
    ];
    $endpoint = ENDPOINT . '?' . http_build_query($query_params);
    $signature = generate_signature(CLIENT_SECRET, $timestamp, $endpoint);
    $headers = [
        'Content-Type: application/json',
        'x-client-id: ' . CLIENT_ID,
        'x-timestamp: ' . $timestamp,
        'x-signature: ' . $signature,
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, BASE_URL . $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    print_r($data);
}

main();
?>
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    private static final String BASE_URL = "https://api.fonbnk.com";
    private static final String ENDPOINT = "/api/onramp/best-offer";
    private static final String CLIENT_ID = "";
    private static final String CLIENT_SECRET = "";

    public static void main(String[] args) throws Exception {
        long timestamp = System.currentTimeMillis();
        Map<String, String> queryParams = new HashMap<>();
        queryParams.put("countryIsoCode", "NG");
        queryParams.put("amount", "10");
        queryParams.put("currency", "crypto");
        queryParams.put("network", "CELO");
        queryParams.put("asset", "CUSD");
        queryParams.put("paymentChannel", "bank");

        String endpoint = ENDPOINT + "?" + getQuery(queryParams);
        String signature = generateSignature(CLIENT_SECRET, String.valueOf(timestamp), endpoint);

        URL url = new URL(BASE_URL + endpoint);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("x-client-id", CLIENT_ID);
        connection.setRequestProperty("x-timestamp", String.valueOf(timestamp));
        connection.setRequestProperty("x-signature", signature);

        Scanner scanner = new Scanner(connection.getInputStream());
        String response = scanner.useDelimiter("\\A").next();
        System.out.println(response);
        scanner.close();
    }

    private static String padBase64(String base64String) {
        return base64String + "=".repeat((4 - base64String.length() % 4) % 4);
    }

    private static String generateSignature(String clientSecret, String timestamp, String endpoint) throws Exception {
        String clientSecretPadded = padBase64(clientSecret);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(clientSecretPadded), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        String data = timestamp + ":" + endpoint;
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hmacBytes);
    }

    private static String getQuery(Map<String, String> params) throws Exception {
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (result.length() > 0) {
                result.append("&");
            }
            result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            result.append("=");
            result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }
        return result.toString();
    }
}
```

{% endtab %}

{% tab title="Dart" %}

```dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

void main() async {
  const String BASE_URL = "https://api.fonbnk.com";
  const String ENDPOINT = "/api/onramp/best-offer";
  const String CLIENT_ID = "";
  const String CLIENT_SECRET = "";

  // Get the current timestamp in milliseconds
  int timestamp = DateTime.now().millisecondsSinceEpoch;

  // Create query parameters
  Map<String, String> queryParams = {
    "countryIsoCode": "NG",
    "amount": "10",
    "currency": "crypto",
    "network": "CELO",
    "asset": "CUSD",
    "paymentChannel": "bank",
  };

  // Generate the query string
  String queryString = getQuery(queryParams);

  // Create the endpoint with query parameters
  String endpoint = ENDPOINT + "?" + queryString;

  // Generate the signature
  String signature = generateSignature(CLIENT_SECRET, timestamp.toString(), endpoint);

  // Build the URL
  String url = BASE_URL + endpoint;

  // Set up the HTTP GET request
  var headers = {
    "Content-Type": "application/json",
    "x-client-id": CLIENT_ID,
    "x-timestamp": timestamp.toString(),
    "x-signature": signature,
  };

  // Send the GET request
  var response = await http.get(Uri.parse(url), headers: headers);

  // Print the response body
  print(response.body);
}

String getQuery(Map<String, String> params) {
  return params.entries
      .map((entry) =>
  Uri.encodeQueryComponent(entry.key) + "=" + Uri.encodeQueryComponent(entry.value))
      .join("&");
}

String generateSignature(String clientSecret, String timestamp, String endpoint) {
  // Use the custom lenient Base64 decoder
  List<int> secretKey = lenientBase64Decode(clientSecret);

  Hmac hmac = Hmac(sha256, secretKey);
  String data = '$timestamp:$endpoint';
  Digest digest = hmac.convert(utf8.encode(data));

  // Encode the signature using Base64
  String signature = base64Encode(digest.bytes);
  return signature;
}

List<int> lenientBase64Decode(String input) {
  // Base64 index table
  const String base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  // Remove all characters that are not in the Base64 alphabet
  String sanitizedInput = input.replaceAll(RegExp(r'[^A-Za-z0-9+/]'), '');

  // Map each character to its Base64 index
  List<int> buffer = [];
  int bits = 0;
  int bitsCount = 0;

  for (int i = 0; i < sanitizedInput.length; i++) {
    int val = base64Chars.indexOf(sanitizedInput[i]);
    if (val < 0) {
      // Skip invalid characters
      continue;
    }
    bits = (bits << 6) | val;
    bitsCount += 6;
    if (bitsCount >= 8) {
      bitsCount -= 8;
      int byte = (bits >> bitsCount) & 0xFF;
      buffer.add(byte);
    }
  }

  return buffer;
}
```

{% endtab %}

{% tab title="Elixir" %}

```elixir
Mix.install([
  {:httpoison, "~> 1.8"},
  {:jason, "~> 1.4"}
])

defmodule FonbnkClient do
  @moduledoc """
  A client for interacting with the Fonbnk API.
  """

  @base_url "https://api.fonbnk.com"
  @endpoint "/api/onramp/best-offer"
  @client_id ""
  @client_secret ""

  def pad_base64(base64_string) do
    pad_length = Integer.mod(-String.length(base64_string), 4)
    base64_string <> String.duplicate("=", pad_length)
  end

  def generate_signature(client_secret, timestamp, endpoint) do
    client_secret_padded = pad_base64(client_secret)
    {:ok, client_secret_decoded} = Base.decode64(client_secret_padded)
    message = "#{timestamp}:#{endpoint}"
    hmac = :crypto.mac(:hmac, :sha256, client_secret_decoded, message)
    Base.encode64(hmac)
  end

  def main do
    timestamp = :os.system_time(:millisecond) |> Integer.to_string()
    query_params = %{
      "countryIsoCode" => "NG",
      "amount" => "10",
      "currency" => "crypto",
      "network" => "CELO",
      "asset" => "CUSD",
      "paymentChannel" => "bank"
    }

    encoded_query = URI.encode_query(query_params)
    endpoint = @endpoint <> "?" <> encoded_query
    signature = generate_signature(@client_secret, timestamp, endpoint)

    headers = [
      {"Content-Type", "application/json"},
      {"x-client-id", @client_id},
      {"x-timestamp", timestamp},
      {"x-signature", signature}
    ]

    url = @base_url <> endpoint

    case HTTPoison.get(url, headers) do
      {:ok, %HTTPoison.Response{body: body, status_code: code}} when code in 200..299 ->
        data = Jason.decode!(body)
        IO.inspect(data)

      {:ok, %HTTPoison.Response{body: body, status_code: code}} ->
        IO.puts("HTTP Error #{code}: #{body}")

      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts("Request Error: #{inspect(reason)}")
    end
  end
end

FonbnkClient.main()
```

{% endtab %}
{% endtabs %}


# Endpoints


# On Ramp

On-ramp

## Get best offer

> Returns the best offer for the provided country, network, asset, amount and payment channel.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]},"WidgetAmountCurrency":{"type":"string","enum":["local","crypto"]},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampBestOfferResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"offer":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"exchangeRate":{"type":"number","description":"Exchange rate for the order"},"cryptoExchangeRate":{"type":"number","description":"Exchange rate for the crypto amount"},"requiredFields":{"type":"object","description":"Data required to submit the order","additionalProperties":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user must to pay"},"totalAmountUsd":{"type":"number","description":"Total amount of local currency in USD user must to pay"},"totalAmountCrypto":{"type":"number","description":"Total amount of local currency in crypto user must to pay"},"withdrawAmountUsd":{"type":"number","description":"Amount in USD user will receive after the order is completed"},"withdrawAmountCrypto":{"type":"number","description":"Amount in crypto user will receive after the order is completed"},"feePercent":{"type":"number","description":"Total fee percent (fonbnk fee + partner fee)"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feePercentFonbnk":{"type":"number","description":"Fonbnk fee percent"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feePercentPartner":{"type":"number","description":"Partner fee percent"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"gasAmountCrypto":{"type":"number","description":"Gas fee amount in crypto"}}}}},"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}},"paths":{"/api/onramp/best-offer":{"get":{"tags":["on-ramp"],"summary":"Get best offer","description":"Returns the best offer for the provided country, network, asset, amount and payment channel.","operationId":"getOnrampBestOffer","parameters":[{"name":"network","in":"query","required":true,"description":"blockchain network of the order","schema":{"$ref":"#/components/schemas/OnRampNetwork"}},{"name":"asset","in":"query","required":true,"description":"asset of the order","schema":{"$ref":"#/components/schemas/OnRampAsset"}},{"name":"currency","in":"query","required":true,"description":"Currency of the amount param","schema":{"$ref":"#/components/schemas/WidgetAmountCurrency"}},{"name":"amount","in":"query","required":true,"description":"Amount of local currency user wants to pay or amount of crypto user wants to receive depending on the currency param value","schema":{"type":"number"}},{"name":"countryIsoCode","in":"query","required":true,"description":"country ISO code, e.g. NG, KE etc.","schema":{"type":"string"}},{"name":"paymentChannel","in":"query","required":true,"description":"type of the payment channel user wants to use, e.g. bank, mobile_money, airtime to pay local currency","schema":{"$ref":"#/components/schemas/OnRampPaymentChannel"}},{"name":"carrierCode","in":"query","required":false,"description":"carrier code if applicable, e.g. for mobile_money or airtime orders","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampBestOfferResponse"}}}}}}}}}
```

## Get payment channels

> Returns a list of supported countries and their payment channels

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampPaymentChannelListResponse":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"ISO code of the local currency, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"name":{"type":"string","description":"Name of the country, e.g. Nigeria, Kenya"},"paymentChannels":{"type":"array","description":"List of payment channels available for the country","items":{"type":"object","properties":{"paymentChannel":{"type":"string","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"description":{"type":"string","description":"Description of the payment channel"},"requiresCarrier":{"type":"boolean","description":"Indicates if the payment channel requires a carrier ID"},"carriers":{"type":"array","description":"List of carriers available for the payment channel","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the carrier"},"name":{"type":"string","description":"Name of the carrier, e.g. Safaricom, MTN"},"code":{"type":"string","description":"Code of the carrier, e.g. ng_mtn, ke_safaricom"}}}}}}}}}}}},"paths":{"/api/onramp/payment-channels":{"get":{"tags":["on-ramp"],"summary":"Get payment channels","description":"Returns a list of supported countries and their payment channels","operationId":"getOnrampPaymentChannels","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampPaymentChannelListResponse"}}}}}}}}}
```

## Get limits

> Returns minimum and maximum amount of order in crypto and local currency and applied fees for specific payment channel, country, network and asset.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number","description":"Minimum amount in USD for the order"},"maxUsd":{"type":"number","description":"Maximum amount in USD for the order"},"minLocalCurrency":{"type":"number","description":"Minimum amount in local currency for the order"},"maxLocalCurrency":{"type":"number","description":"Maximum amount in local currency for the order"},"minCrypto":{"type":"number","description":"Minimum amount in crypto for the order"},"maxCrypto":{"type":"number","description":"Maximum amount in crypto for the order"}}}}},"paths":{"/api/onramp/limits":{"get":{"tags":["on-ramp"],"summary":"Get limits","description":"Returns minimum and maximum amount of order in crypto and local currency and applied fees for specific payment channel, country, network and asset.","operationId":"getOnrampLimits","parameters":[{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnRampNetwork"}},{"name":"asset","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OnRampAsset"}},{"name":"countryIsoCode","in":"query","required":true,"schema":{"type":"string"}},{"name":"paymentChannel","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnRampPaymentChannel"}},{"name":"carrierCode","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampLimitsResponse"}}}}}}}}}
```

## Get list of supported assets

> Returns a list of supported blockchain assets for the on-ramp orders

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/onramp/assets":{"get":{"tags":["on-ramp"],"summary":"Get list of supported assets","description":"Returns a list of supported blockchain assets for the on-ramp orders","operationId":"getOnrampAssets","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OnRampNetwork"},"asset":{"$ref":"#/components/schemas/OnRampAsset"}}}}}}}}}}}}
```

## Get order

> Returns a single pay widget order by its ID or orderParams query parameter.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/onramp/order":{"get":{"tags":["on-ramp"],"summary":"Get order","description":"Returns a single pay widget order by its ID or orderParams query parameter.","operationId":"getOnrampOrderById","parameters":[{"name":"orderId","in":"query","description":"id of the order which you could receive via a webhook or iframe events","required":false,"schema":{"type":"string"}},{"name":"orderParams","in":"query","required":false,"description":"Value which you provided in the orderParams parameter of the pay widget URL","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampOrder"}}}}}}}}}
```

## Get orders

> Returns a paginated list of pay widget orders. Filters can be applied to the list by providing query parameters.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"},"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]},"PaginatedOnrampOrders":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OnRampOrder"}}}}]},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/onramp/orders":{"get":{"tags":["on-ramp"],"summary":"Get orders","description":"Returns a paginated list of pay widget orders. Filters can be applied to the list by providing query parameters.","operationId":"getOnrampOrders","parameters":[{"name":"cursor","in":"query","description":"this parameter should be provided in order to get a next page from the pagination, it should be taken from \"nextCursor\" response value","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"number from 1 to 100, describes how many records should be in each pagination page","required":true,"schema":{"type":"integer"}},{"name":"network","in":"query","description":"blockchain network of orders","required":false,"schema":{"$ref":"#/components/schemas/OnRampNetwork"}},{"name":"address","in":"query","required":false,"schema":{"type":"string"}},{"name":"userPhoneNumber","in":"query","description":"phone number of the client, should include country code","required":false,"schema":{"type":"string"}},{"name":"userEmail","in":"query","description":"email of the client","required":false,"schema":{"type":"string"}},{"name":"paymentChannel","in":"query","required":false,"description":"type of the payment channel","schema":{"$ref":"#/components/schemas/OnRampPaymentChannel"}},{"name":"buySwapStatus","in":"query","description":"status of a buy swap","required":false,"schema":{"$ref":"#/components/schemas/BuySwapStatus"}},{"name":"withdrawalStatus","in":"query","description":"status of a crypto transfer","required":false,"schema":{"$ref":"#/components/schemas/WithdrawalStatus"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedOnrampOrders"}}}}}}}}}
```

## Create order

> Creates a new on-ramp order using a quote ID from the best offer endpoint

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"WidgetAmountCurrency":{"type":"string","enum":["local","crypto"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]},"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]}}},"paths":{"/api/onramp/order/create":{"post":{"tags":["on-ramp"],"summary":"Create order","description":"Creates a new on-ramp order using a quote ID from the best offer endpoint","operationId":"createOnrampOrder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quoteId","network","amount","currency","asset","email","userIp"],"properties":{"quoteId":{"type":"string","description":"Quote ID from the best offer endpoint"},"network":{"$ref":"#/components/schemas/OnRampNetwork"},"amount":{"type":"number","description":"Amount based on the currency parameter"},"currency":{"$ref":"#/components/schemas/WidgetAmountCurrency"},"asset":{"$ref":"#/components/schemas/OnRampAsset"},"address":{"type":"string","description":"Wallet address to receive the crypto"},"email":{"type":"string","format":"email","description":"User email address"},"orderParams":{"type":"string","description":"Optional parameter for order tracking"},"extraFields":{"type":"object","description":"Additional fields required for the order","additionalProperties":true},"userIp":{"type":"string","format":"ipv4","description":"User's IP address"},"redirectUrl":{"type":"string","format":"uri","description":"URL to redirect the user after completion of the transfer, applicable only for orders with transferInstructions.type = \"redirect\""}}}}}},"responses":{"200":{"description":"Order created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampOrder"}}}}}}}}}
```

## Verify OTP for order

> Verifies OTP code for an on-ramp order that requires OTP authentication.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}}},"paths":{"/api/onramp/order/otp":{"post":{"tags":["on-ramp"],"summary":"Verify OTP for order","description":"Verifies OTP code for an on-ramp order that requires OTP authentication.","operationId":"verifyOnrampOrderOtp","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId","otp"],"properties":{"orderId":{"type":"string","description":"ID of the order to verify OTP for"},"otp":{"type":"string","description":"OTP code received by the user"}}}}}}}}}}
```

## Confirm onramp order

> Confirms an onramp order

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{},"schemas":{"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/onramp/order/confirm":{"post":{"tags":["on-ramp"],"summary":"Confirm onramp order","description":"Confirms an onramp order","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId"],"properties":{"orderId":{"type":"string","description":"The ID of the order to confirm"}}}}}},"responses":{"200":{"description":"Order confirmed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampOrder"}}}}}}}}}
```

## Reject onramp order

> Rejects an onramp order

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{},"schemas":{"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/onramp/order/reject":{"post":{"tags":["on-ramp"],"summary":"Reject onramp order","description":"Rejects an onramp order","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId"],"properties":{"orderId":{"type":"string","description":"The ID of the order to reject"}}}}}},"responses":{"200":{"description":"Order rejected successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnRampOrder"}}}}}}}}}
```


# Off Ramp

Off-ramp

## Get best offer

> Returns the best offer for the provided country, network, asset, amount and payment channel.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"WidgetAmountCurrency":{"type":"string","enum":["local","crypto"]},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]},"OfframpBestOfferResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"offer":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"exchangeRate":{"type":"number","description":"Exchange rate for the order"},"cryptoExchangeRate":{"type":"number","description":"Exchange rate for the crypto amount"},"requiredFields":{"type":"object","description":"Data required to submit the order","additionalProperties":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user will receive"},"usdAmount":{"type":"number","description":"Amount in USD user must pay"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"cryptoAmount":{"type":"number","description":"Amount in crypto user must pay"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"}}}}},"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}},"paths":{"/api/offramp/best-offer":{"get":{"tags":["off-ramp"],"summary":"Get best offer","description":"Returns the best offer for the provided country, network, asset, amount and payment channel.","operationId":"getOfframpBestOffer","parameters":[{"name":"amount","in":"query","description":"Amount of usd user wants to pay or amount of local currency user wants to receive depending on the currency param value","required":true,"schema":{"type":"number"}},{"name":"currency","in":"query","description":"Currency of the amount param","required":true,"schema":{"$ref":"#/components/schemas/WidgetAmountCurrency"}},{"name":"countryIsoCode","in":"query","description":"country ISO code, for example KE for Kenya, NG for Nigeria","required":true,"schema":{"type":"string"}},{"name":"paymentChannel","in":"query","required":true,"description":"type of the payment channel user wants to use, e.g. bank, mobile_money, airtime to receive local currency","schema":{"$ref":"#/components/schemas/OffRampPaymentChannel"}},{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OffRampNetwork"}},{"name":"asset","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OffRampAsset"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpBestOfferResponse"}}}}}}}}}
```

## Get payment channels list

> Returns a list of supported countries and their payment channels

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OffRampPaymentChannelsResponse":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"ISO code of the local currency, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"name":{"type":"string","description":"Name of the country, e.g. Nigeria, Kenya"},"paymentChannels":{"type":"array","description":"List of payment channels available for the country","items":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"description":{"type":"string","description":"Description of the payment channel, e.g. Bank Transfer, Mobile Money"},"carriers":{"type":"array","description":"List of carriers available for the payment channel","items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the carrier"},"name":{"type":"string","description":"Name of the carrier, e.g. Safaricom, MTN"},"code":{"type":"string","description":"Code of the carrier, e.g. ng_mtn, ke_safaricom"}}}}}}}}}},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}},"paths":{"/api/offramp/payment-channels":{"get":{"tags":["off-ramp"],"summary":"Get payment channels list","description":"Returns a list of supported countries and their payment channels","operationId":"getOfframpPaymentChannels","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OffRampPaymentChannelsResponse"}}}}}}}}}}
```

## Get off-ramp limits

> Returns minimum and maximum amount of order in USD and local currency with applied fees for specific payment channel, country, network and asset.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]},"OffRampLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number"},"maxUsd":{"type":"number"},"minLocalCurrency":{"type":"number"},"maxLocalCurrency":{"type":"number"}}}}},"paths":{"/api/offramp/limits":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp limits","description":"Returns minimum and maximum amount of order in USD and local currency with applied fees for specific payment channel, country, network and asset.","operationId":"getOfframpLimits","parameters":[{"name":"paymentChannel","in":"query","description":"payment channel type","required":true,"schema":{"$ref":"#/components/schemas/OffRampPaymentChannel"}},{"name":"countryIsoCode","in":"query","description":"country ISO code","required":true,"schema":{"type":"string"}},{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OffRampNetwork"}},{"name":"asset","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OffRampAsset"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffRampLimitsResponse"}}}}}}}}}
```

## Get supported blockchain assets

> Returns a list of supported wallet networks and their assets for crypto wallet orders

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OffRampWallet":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OffRampNetwork"},"asset":{"$ref":"#/components/schemas/OffRampAsset"}}},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]}}},"paths":{"/api/offramp/assets":{"get":{"tags":["off-ramp"],"summary":"Get supported blockchain assets","description":"Returns a list of supported wallet networks and their assets for crypto wallet orders","responses":{"200":{"description":"A list of supported wallet networks and their assets","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OffRampWallet"}}}}}}}}}}
```

## Get off-ramp order

> Returns a single order by its ID.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OffRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the off-ramp order"},"network":{"$ref":"#/components/schemas/OffRampNetwork","description":"Blockchain network of the off-ramp order, e.g. AVALANCHE, POLYGON, CELO, ETHEREUM"},"asset":{"$ref":"#/components/schemas/OffRampAsset","description":"Blockchain asset of the off-ramp order, e.g. USDC, USDT, CUSD, CKES"},"exchangeRate":{"type":"number","description":"Exchange rate for the off-ramp order"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user will receive after the order is completed"},"usdAmount":{"type":"number","description":"Amount in USD user paid"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"cryptoAmount":{"type":"number","description":"Amount in crypto user paid"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"}}},"fromAddress":{"type":"string","description":"User wallet address from which the off-ramp order was initiated"},"toAddress":{"type":"string","description":"The wallet address to which the crypto was sent"},"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order"},"createdAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order was created"},"expiresAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order expires if it is not paid in time"},"hash":{"type":"string","description":"Transaction hash if available"},"statusHistory":{"type":"array","description":"History of status changes for the off-ramp order","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order at the time of the change"},"changedAt":{"type":"string","format":"date-time","description":"Date and time when the status was changed"}}}},"requiredFields":{"type":"object","description":"Fields that were provided by the user"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"userPhoneNumber":{"type":"string","description":"Phone number of the user, should include country code"},"userEmail":{"type":"string","description":"Email address of the user"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"offerRequiredFields":{"type":"array","description":"Pretty formatted required fields were provided by the user to display on the merchant side","items":{"type":"object","properties":{"label":{"type":"string","description":"Label of the required field"},"type":{"type":"string","description":"Type of the required field, e.g. number, string, date, boolean, email, enum"},"value":{"type":"string","description":"Value of the required field"}}}},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel used for the off-ramp order, e.g. bank, mobile_money, airtime"}}},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]},"OffRampOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}},"paths":{"/api/offramp/order/{id}":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp order","description":"Returns a single order by its ID.","operationId":"getOfframpOrderById","parameters":[{"name":"id","in":"path","required":true,"description":"ID of the off-ramp order","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffRampOrder"}}}}}}}}}
```

## Get off-ramp orders

> Returns a paginated list of orders. Filters can be applied to the list by providing query parameters.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OffRampOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OffRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the off-ramp order"},"network":{"$ref":"#/components/schemas/OffRampNetwork","description":"Blockchain network of the off-ramp order, e.g. AVALANCHE, POLYGON, CELO, ETHEREUM"},"asset":{"$ref":"#/components/schemas/OffRampAsset","description":"Blockchain asset of the off-ramp order, e.g. USDC, USDT, CUSD, CKES"},"exchangeRate":{"type":"number","description":"Exchange rate for the off-ramp order"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user will receive after the order is completed"},"usdAmount":{"type":"number","description":"Amount in USD user paid"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"cryptoAmount":{"type":"number","description":"Amount in crypto user paid"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"}}},"fromAddress":{"type":"string","description":"User wallet address from which the off-ramp order was initiated"},"toAddress":{"type":"string","description":"The wallet address to which the crypto was sent"},"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order"},"createdAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order was created"},"expiresAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order expires if it is not paid in time"},"hash":{"type":"string","description":"Transaction hash if available"},"statusHistory":{"type":"array","description":"History of status changes for the off-ramp order","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order at the time of the change"},"changedAt":{"type":"string","format":"date-time","description":"Date and time when the status was changed"}}}},"requiredFields":{"type":"object","description":"Fields that were provided by the user"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"userPhoneNumber":{"type":"string","description":"Phone number of the user, should include country code"},"userEmail":{"type":"string","description":"Email address of the user"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"offerRequiredFields":{"type":"array","description":"Pretty formatted required fields were provided by the user to display on the merchant side","items":{"type":"object","properties":{"label":{"type":"string","description":"Label of the required field"},"type":{"type":"string","description":"Type of the required field, e.g. number, string, date, boolean, email, enum"},"value":{"type":"string","description":"Value of the required field"}}}},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel used for the off-ramp order, e.g. bank, mobile_money, airtime"}}}}},"paths":{"/api/offramp/orders":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp orders","description":"Returns a paginated list of orders. Filters can be applied to the list by providing query parameters.","operationId":"getOfframpOrders","parameters":[{"name":"cursor","in":"query","description":"this parameter should be provided in order to get a next page from the pagination, it should be taken from \"nextCursor\" response value","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"number from 1 to 100, describes how many records should be in each pagination page","required":true,"schema":{"type":"integer"}},{"name":"network","in":"query","required":false,"description":"blockchain network of orders","schema":{"$ref":"#/components/schemas/OffRampNetwork"}},{"name":"asset","in":"query","required":false,"description":"asset of orders","schema":{"$ref":"#/components/schemas/OffRampAsset"}},{"name":"fromAddress","in":"query","description":"address of a user wallet","required":false,"schema":{"type":"string"}},{"name":"userPhoneNumber","in":"query","description":"phone number of the client, should include country code","required":false,"schema":{"type":"string"}},{"name":"userEmail","in":"query","description":"email of the client","required":false,"schema":{"type":"string"}},{"name":"hash","in":"query","description":"hash of the user transaction","required":false,"schema":{"type":"string"}},{"name":"countryIsoCode","in":"query","description":"country ISO code, e.g. NG","required":false,"schema":{"type":"string"}},{"name":"paymentChannel","in":"query","description":"type of the payment channel","required":false,"schema":{"$ref":"#/components/schemas/OffRampPaymentChannel"}},{"name":"orderParams","in":"query","description":"value of the orderParams query param during order creation","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"status of the order","required":false,"schema":{"$ref":"#/components/schemas/OffRampOrderStatus"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OffRampOrder"}}}}]}}}}}}}}}
```


# Util

Utility

## Check address

> Check if the provided wallet address was used in the Fonbnk system

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}}},"paths":{"/api/util/check-address":{"post":{"tags":["util"],"summary":"Check address","description":"Check if the provided wallet address was used in the Fonbnk system","operationId":"checkAddress","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"}}}}}},"responses":{"200":{"description":"Address usage status","content":{"application/json":{"schema":{"type":"object","properties":{"used":{"type":"boolean"}}}}}}}}}}}
```

## Get supported countries with KYC rules

> Returns a list of supported countries with KYC rules depends on order amount

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"KycDocument":{"type":"object","properties":{"_id":{"type":"string"},"type":{"type":"string","enum":["basic","advanced"]},"title":{"type":"string"},"value":{"type":"string"},"requiredFields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KycRequiredField"}}}},"KycRequiredField":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/KycRequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"format":{"type":"string"},"regexp":{"type":"string"},"regexpFlags":{"type":"string"}}},"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]},"KycType":{"type":"string","enum":["basic","advanced"]}}},"paths":{"/api/util/countries":{"get":{"tags":["util"],"summary":"Get supported countries with KYC rules","description":"Returns a list of supported countries with KYC rules depends on order amount","operationId":"countries","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"ISO code of the local currency, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"name":{"type":"string","description":"Name of the country, e.g. Nigeria, Kenya"},"basicDocuments":{"type":"array","description":"List of documents required for basic KYC","items":{"$ref":"#/components/schemas/KycDocument","description":"Name of the document, e.g. \"National ID\", \"Passport\""}},"advancedDocuments":{"type":"array","description":"List of documents required for advanced KYC","items":{"$ref":"#/components/schemas/KycDocument","description":"Name of the document, e.g. \"National ID\", \"Passport\""}},"kycRules":{"type":"object","properties":{"onramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number","description":"Minimum amount in USD for KYC"},"max":{"type":"number","description":"Maximum amount in USD for KYC (exclusive)"},"type":{"$ref":"#/components/schemas/KycType"}}}},"offramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number","description":"Minimum amount in USD for KYC"},"max":{"type":"number","description":"Maximum amount in USD for KYC (exclusive)"},"type":{"$ref":"#/components/schemas/KycType"}}}}}}}}}}}}}}}}}
```

## Get supported carriers

> Returns a list of supported carriers

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}}},"paths":{"/api/util/carriers":{"get":{"tags":["util"],"summary":"Get supported carriers","description":"Returns a list of supported carriers","operationId":"carriers","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"carriers":{"type":"array","description":"List of carriers available for the payment channel","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the carrier, e.g. Safaricom, MTN"},"code":{"type":"string","description":"Code of the carrier, e.g. ng_mtn, ke_safaricom"}}}}}}}}}}}}}}}
```

## Get supported blockchain assets

> Returns a list of supported blockchain assets for the off-ramp and on-ramp

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}},"paths":{"/api/util/assets":{"get":{"tags":["util"],"summary":"Get supported blockchain assets","description":"Returns a list of supported blockchain assets for the off-ramp and on-ramp","operationId":"blockchainAssets","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OnRampNetwork"},"asset":{"$ref":"#/components/schemas/OnRampAsset"},"canOnramp":{"type":"boolean"},"canOfframp":{"type":"boolean"}}}}}}}}}}}}
```


# User

User

## Generate user authentication tokens

> Generates authentication tokens for a user. Creates a new user if one doesn't exist with the provided email. This feature is disabled by default and can be enabled by contacting Fonbnk support.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"user","description":"User"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}}},"paths":{"/api/user/tokens":{"post":{"tags":["user"],"summary":"Generate user authentication tokens","description":"Generates authentication tokens for a user. Creates a new user if one doesn't exist with the provided email. This feature is disabled by default and can be enabled by contacting Fonbnk support.","operationId":"generateUserTokens","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","countryIsoCode"],"properties":{"email":{"type":"string","description":"Email of a user"},"countryIsoCode":{"type":"string","description":"Country code"}}}}}},"responses":{"200":{"description":"Successfully generated user tokens","content":{"application/json":{"schema":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT access token for user authentication"},"refreshToken":{"type":"string","description":"JWT refresh token for token renewal"}}}}}}}}}}}
```

## Get User KYC Status

> Retrieves the Know Your Customer (KYC) status for a given user. If the user does not exist, a new user will be created with the provided email and country ISO code.  This feature is disabled by default and can be enabled by contacting Fonbnk support.<br>

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"user","description":"User"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}},"schemas":{"KycStatus":{"type":"object","properties":{"passedKycType":{"type":"string","description":"The type of KYC the user has passed."},"kycStatus":{"type":"string","enum":["initiated","approved","rejected","invalid"],"description":"The current KYC status of the user."},"kycStatusDescription":{"type":"string","description":"A description of the KYC status."},"reachedKycLimit":{"type":"boolean","description":"Indicates if the user has reached their KYC limit."},"basicDocuments":{"type":"array","items":{"$ref":"#/components/schemas/KycDocument"}},"advancedDocuments":{"type":"array","items":{"$ref":"#/components/schemas/KycDocument"}},"kycRules":{"$ref":"#/components/schemas/KycRules"}}},"KycDocument":{"type":"object","properties":{"_id":{"type":"string"},"type":{"type":"string","enum":["basic","advanced"]},"title":{"type":"string"},"value":{"type":"string"},"requiredFields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KycRequiredField"}}}},"KycRequiredField":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/KycRequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"format":{"type":"string"},"regexp":{"type":"string"},"regexpFlags":{"type":"string"}}},"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]},"KycRules":{"type":"object","properties":{"onramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}},"offramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}}}}}},"paths":{"/api/user/kyc/status":{"post":{"summary":"Get User KYC Status","description":"Retrieves the Know Your Customer (KYC) status for a given user. If the user does not exist, a new user will be created with the provided email and country ISO code.  This feature is disabled by default and can be enabled by contacting Fonbnk support.\n","tags":["user"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","countryIsoCode"],"properties":{"email":{"type":"string","format":"email","description":"The user's email address."},"countryIsoCode":{"type":"string","description":"The ISO 3166-1 alpha-2 country code for the user."}}}}}},"responses":{"200":{"description":"Successfully retrieved the user's KYC status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KycStatus"}}}}}}}}}
```

## Submit User KYC Information

> Submits Know Your Customer (KYC) information for a user. This feature is disabled by default and can be enabled by contacting Fonbnk support.<br>

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"user","description":"User"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://api.fonbnk.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id","description":"Your client ID"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp","description":"The Unix timestamp of the request signature"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature","description":"HMAC-SHA256 signature generated using your secret"}}},"paths":{"/api/user/kyc/submit":{"post":{"summary":"Submit User KYC Information","description":"Submits Know Your Customer (KYC) information for a user. This feature is disabled by default and can be enabled by contacting Fonbnk support.\n","tags":["user"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","documentId","userFields"],"properties":{"email":{"type":"string","format":"email","description":"The email address of the user"},"documentId":{"type":"string","description":"ID of the KYC document type being submitted"},"userFields":{"type":"object","description":"KYC fields data for the user","additionalProperties":{"type":"string"}}}}}}},"responses":{"200":{"description":"Successfully submitted KYC information","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}}}}
```


# Models

## The KycRequiredFieldType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]}}}}
```

## The OnRampNetwork object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]}}}}
```

## The OnRampAsset object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}}}
```

## The OnRampOrderStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"}}}}
```

## The BuySwapStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"}}}}
```

## The OnRampPaymentChannel object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]}}}}
```

## The WithdrawalStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]}}}}
```

## The OnRampOrder object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}}}
```

## The Paginated object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}}}}}
```

## The PaginatedOnrampOrders object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"PaginatedOnrampOrders":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OnRampOrder"}}}}]},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OnRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"status":{"$ref":"#/components/schemas/OnRampOrderStatus"},"date":{"type":"string","format":"date-time","description":"Date of order creation"},"orderId":{"type":"string","description":"Order ID"},"phoneNumber":{"type":"string","description":"User phone number, should include country code"},"email":{"type":"string","description":"User email address"},"localCurrencyAmount":{"type":"number","description":"Amount in local currency"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN, KES"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG"},"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Payment channel used for the order, e.g. bank, mobile_money, airtime"},"amount":{"type":"number","description":"Amount in USD"},"amountCrypto":{"type":"number","description":"Amount in crypto"},"network":{"$ref":"#/components/schemas/OnRampNetwork","description":"Blockchain network of the order, e.g. POLYGON, ETHEREUM, STELLAR"},"asset":{"$ref":"#/components/schemas/OnRampAsset","description":"Blockchain asset of the order, e.g. USDC, USDT, CUSD"},"address":{"type":"string","description":"User wallet address"},"memo":{"type":"string","description":"Memo for the Stellar, TON and other networks transactions that support it"},"hash":{"type":"string","description":"Transaction hash if available"},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"resumeUrl":{"type":"string","description":"URL to resume the order in the pay widget"},"carrierId":{"type":"string","description":"Carrier ID if applicable, e.g. for mobile money orders"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"feePercentFonbnk":{"type":"number","description":"fonbnk fee percent"},"feePercentPartner":{"type":"number","description":"partner fee percent"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"transferInstructions":{"type":"object","properties":{"type":{"type":"string","description":"Type of action required to complete the order","enum":["manual","redirect","stk_push","otp_stk_push","ussd"]},"ussdCode":{"type":"string","description":"USSD code a user needs to dial to complete the order, present only if type is \"ussd\". May include \"{pin}\" placeholder for user PIN, e.g. \"*123*{pin}#\" in this case it should be replaced with their PIN"},"paymentUrl":{"type":"string","description":"URL to redirect the user to complete the payment, present only if type is \"redirect\""},"instructionsText":{"type":"string","description":"Text with instructions for the user to complete the order"},"warningText":{"type":"string","description":"Warning text for the user"},"transferDetails":{"type":"object","properties":{"key":{"type":"object","properties":{"label":{"type":"string","description":"Label for the transfer detail"},"description":{"type":"string","description":"Description of the transfer detail"},"value":{"type":"string","description":"Value of the transfer detail, e.g. account number, phone number, etc."}}}}}}}}},"OnRampOrderStatus":{"type":"string","enum":["swap_initiated","swap_expired","swap_buyer_rejected","swap_buyer_confirmed","swap_seller_rejected","swap_seller_confirmed","pending","complete","failed"],"description":"- swap_initiated: user has created an order\n- swap_expired: an order has expired\n- swap_buyer_rejected: user has rejected an order\n- swap_buyer_confirmed: user has confirmed an order\n- swap_seller_rejected: agent has rejected an order, happens when agent don't receive a payment\n- swap_seller_confirmed: agent has confirmed an order\n- pending: crypto transaction is pending\n- complete: crypto transaction is complete\n- failed: crypto transaction has failed"},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"OnRampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","CELO","BASE","TON","TRON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS","USDE"]}}}}
```

## The OnRampBestOfferResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampBestOfferResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"offer":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OnRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"exchangeRate":{"type":"number","description":"Exchange rate for the order"},"cryptoExchangeRate":{"type":"number","description":"Exchange rate for the crypto amount"},"requiredFields":{"type":"object","description":"Data required to submit the order","additionalProperties":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user must to pay"},"totalAmountUsd":{"type":"number","description":"Total amount of local currency in USD user must to pay"},"totalAmountCrypto":{"type":"number","description":"Total amount of local currency in crypto user must to pay"},"withdrawAmountUsd":{"type":"number","description":"Amount in USD user will receive after the order is completed"},"withdrawAmountCrypto":{"type":"number","description":"Amount in crypto user will receive after the order is completed"},"feePercent":{"type":"number","description":"Total fee percent (fonbnk fee + partner fee)"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feePercentFonbnk":{"type":"number","description":"Fonbnk fee percent"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feePercentPartner":{"type":"number","description":"Partner fee percent"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"},"gasAmountUsd":{"type":"number","description":"Gas fee amount in USD"},"gasAmountLocalCurrency":{"type":"number","description":"Gas fee amount in local currency"},"gasAmountCrypto":{"type":"number","description":"Gas fee amount in crypto"}}}}},"OnRampPaymentChannel":{"type":"string","enum":["bank","mobile_money","airtime"]},"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}}}
```

## The OnRampPaymentChannelListResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampPaymentChannelListResponse":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"ISO code of the local currency, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"name":{"type":"string","description":"Name of the country, e.g. Nigeria, Kenya"},"paymentChannels":{"type":"array","description":"List of payment channels available for the country","items":{"type":"object","properties":{"paymentChannel":{"type":"string","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"description":{"type":"string","description":"Description of the payment channel"},"requiresCarrier":{"type":"boolean","description":"Indicates if the payment channel requires a carrier ID"},"carriers":{"type":"array","description":"List of carriers available for the payment channel","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the carrier"},"name":{"type":"string","description":"Name of the carrier, e.g. Safaricom, MTN"},"code":{"type":"string","description":"Code of the carrier, e.g. ng_mtn, ke_safaricom"}}}}}}}}}}}}}
```

## The OnRampLimitsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number","description":"Minimum amount in USD for the order"},"maxUsd":{"type":"number","description":"Maximum amount in USD for the order"},"minLocalCurrency":{"type":"number","description":"Minimum amount in local currency for the order"},"maxLocalCurrency":{"type":"number","description":"Maximum amount in local currency for the order"},"minCrypto":{"type":"number","description":"Minimum amount in crypto for the order"},"maxCrypto":{"type":"number","description":"Maximum amount in crypto for the order"}}}}}}
```

## The OffRampOrderStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}}}
```

## The OffRampNetwork object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]}}}}
```

## The OffRampAsset object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]}}}}
```

## The OffRampType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The KycType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycType":{"type":"string","enum":["basic","advanced"]}}}}
```

## The KycStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycStatus":{"type":"object","properties":{"passedKycType":{"type":"string","description":"The type of KYC the user has passed."},"kycStatus":{"type":"string","enum":["initiated","approved","rejected","invalid"],"description":"The current KYC status of the user."},"kycStatusDescription":{"type":"string","description":"A description of the KYC status."},"reachedKycLimit":{"type":"boolean","description":"Indicates if the user has reached their KYC limit."},"basicDocuments":{"type":"array","items":{"$ref":"#/components/schemas/KycDocument"}},"advancedDocuments":{"type":"array","items":{"$ref":"#/components/schemas/KycDocument"}},"kycRules":{"$ref":"#/components/schemas/KycRules"}}},"KycDocument":{"type":"object","properties":{"_id":{"type":"string"},"type":{"type":"string","enum":["basic","advanced"]},"title":{"type":"string"},"value":{"type":"string"},"requiredFields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KycRequiredField"}}}},"KycRequiredField":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/KycRequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"format":{"type":"string"},"regexp":{"type":"string"},"regexpFlags":{"type":"string"}}},"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]},"KycRules":{"type":"object","properties":{"onramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}},"offramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}}}}}}}
```

## The KycDocument object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycDocument":{"type":"object","properties":{"_id":{"type":"string"},"type":{"type":"string","enum":["basic","advanced"]},"title":{"type":"string"},"value":{"type":"string"},"requiredFields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KycRequiredField"}}}},"KycRequiredField":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/KycRequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"format":{"type":"string"},"regexp":{"type":"string"},"regexpFlags":{"type":"string"}}},"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]}}}}
```

## The KycRequiredField object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycRequiredField":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/KycRequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"format":{"type":"string"},"regexp":{"type":"string"},"regexpFlags":{"type":"string"}}},"KycRequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","phone","smile-identity-images"]}}}}
```

## The KycRules object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycRules":{"type":"object","properties":{"onramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}},"offramp":{"type":"array","items":{"type":"object","properties":{"min":{"type":"number"},"max":{"oneOf":[{"type":"number"},{"type":"string"}]},"type":{"type":"string","enum":["none","basic","advanced"]}}}}}}}}}
```

## The OffRampPaymentChannel object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The OffRampOrder object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the off-ramp order"},"network":{"$ref":"#/components/schemas/OffRampNetwork","description":"Blockchain network of the off-ramp order, e.g. AVALANCHE, POLYGON, CELO, ETHEREUM"},"asset":{"$ref":"#/components/schemas/OffRampAsset","description":"Blockchain asset of the off-ramp order, e.g. USDC, USDT, CUSD, CKES"},"exchangeRate":{"type":"number","description":"Exchange rate for the off-ramp order"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user will receive after the order is completed"},"usdAmount":{"type":"number","description":"Amount in USD user paid"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"cryptoAmount":{"type":"number","description":"Amount in crypto user paid"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"}}},"fromAddress":{"type":"string","description":"User wallet address from which the off-ramp order was initiated"},"toAddress":{"type":"string","description":"The wallet address to which the crypto was sent"},"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order"},"createdAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order was created"},"expiresAt":{"type":"string","format":"date-time","description":"Date and time when the off-ramp order expires if it is not paid in time"},"hash":{"type":"string","description":"Transaction hash if available"},"statusHistory":{"type":"array","description":"History of status changes for the off-ramp order","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OffRampOrderStatus","description":"Status of the off-ramp order at the time of the change"},"changedAt":{"type":"string","format":"date-time","description":"Date and time when the status was changed"}}}},"requiredFields":{"type":"object","description":"Fields that were provided by the user"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"userPhoneNumber":{"type":"string","description":"Phone number of the user, should include country code"},"userEmail":{"type":"string","description":"Email address of the user"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"offerRequiredFields":{"type":"array","description":"Pretty formatted required fields were provided by the user to display on the merchant side","items":{"type":"object","properties":{"label":{"type":"string","description":"Label of the required field"},"type":{"type":"string","description":"Type of the required field, e.g. number, string, date, boolean, email, enum"},"value":{"type":"string","description":"Value of the required field"}}}},"orderParams":{"type":"string","description":"Value of the orderParams query param during order creation"},"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel used for the off-ramp order, e.g. bank, mobile_money, airtime"}}},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]},"OffRampOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The RequiredFieldType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}}}
```

## The OfframpBestOfferResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpBestOfferResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"offer":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"countryIsoCode":{"type":"string","description":"Country ISO code, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"Local currency ISO code, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"exchangeRate":{"type":"number","description":"Exchange rate for the order"},"cryptoExchangeRate":{"type":"number","description":"Exchange rate for the crypto amount"},"requiredFields":{"type":"object","description":"Data required to submit the order","additionalProperties":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number","description":"Amount in local currency user will receive"},"usdAmount":{"type":"number","description":"Amount in USD user must pay"},"feeAmountUsd":{"type":"number","description":"Total fee amount in USD"},"feeAmountUsdFonbnk":{"type":"number","description":"Fonbnk fee amount in USD"},"feeAmountUsdPartner":{"type":"number","description":"Partner fee amount in USD"},"feeAmountLocalCurrency":{"type":"number","description":"Total fee amount in local currency"},"feeAmountLocalCurrencyFonbnk":{"type":"number","description":"Fonbnk fee amount in local currency"},"feeAmountLocalCurrencyPartner":{"type":"number","description":"Partner fee amount in local currency"},"cryptoAmount":{"type":"number","description":"Amount in crypto user must pay"},"feeAmountCrypto":{"type":"number","description":"Total fee amount in crypto"},"feeAmountCryptoFonbnk":{"type":"number","description":"Fonbnk fee amount in crypto"},"feeAmountCryptoPartner":{"type":"number","description":"Partner fee amount in crypto"}}}}},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}}}
```

## The WidgetAmountCurrency object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"WidgetAmountCurrency":{"type":"string","enum":["local","crypto"]}}}}
```

## The OffRampLimitsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number"},"maxUsd":{"type":"number"},"minLocalCurrency":{"type":"number"},"maxLocalCurrency":{"type":"number"}}}}}}
```

## The OffRampPaymentChannelsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampPaymentChannelsResponse":{"type":"object","properties":{"countryIsoCode":{"type":"string","description":"ISO code of the country, e.g. NG for Nigeria, KE for Kenya"},"currencyIsoCode":{"type":"string","description":"ISO code of the local currency, e.g. NGN for Nigerian Naira, KES for Kenyan Shilling"},"name":{"type":"string","description":"Name of the country, e.g. Nigeria, Kenya"},"paymentChannels":{"type":"array","description":"List of payment channels available for the country","items":{"type":"object","properties":{"paymentChannel":{"$ref":"#/components/schemas/OffRampPaymentChannel","description":"Type of the payment channel, e.g. bank, mobile_money, airtime"},"description":{"type":"string","description":"Description of the payment channel, e.g. Bank Transfer, Mobile Money"},"carriers":{"type":"array","description":"List of carriers available for the payment channel","items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the carrier"},"name":{"type":"string","description":"Name of the carrier, e.g. Safaricom, MTN"},"code":{"type":"string","description":"Code of the carrier, e.g. ng_mtn, ke_safaricom"}}}}}}}}}},"OffRampPaymentChannel":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The OffRampWallet object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OffRampWallet":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OffRampNetwork"},"asset":{"$ref":"#/components/schemas/OffRampAsset"}}},"OffRampNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OffRampAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES","CGHS"]}}}}
```


# Intro

## Introduction

Welcome to the Fonbnk Pay Widget Documentation!

### Overview

The Fonbnk Pay Widget is a secure and efficient way to facilitate both on-ramp and off-ramp transactions in crypto. It supports integrated and non-integrated methods, making it versatile for various use cases. This documentation will guide you through the setup, integration, and usage of the Fonbnk Pay Widget.

### Key Features

* **P2P Platform**: Connects buyers and sellers of mobile money (Airtime, M-PESA, Bank, etc.).
* **Crypto Payments**: Facilitates transactions in crypto.
* **Multiple Integration Options**: Supports standalone, iframe/webview, and webhook integrations.
* **Customizable**: Configure the widget using URL parameters to suit your needs.

### How on-ramp works

1. **Customer Selection**: The customer selects the source of their fund and the amount of crypto they want to receive.
2. **Wallet Details**: The customer provides their wallet details.
3. **Funds Transfer**: The customer transfers funds to an agent and confirms the order.
4. **Order Confirmation**: The agent confirms the order, and the system sends crypto to the customer's wallet.

### How off-ramp works

Fonbnk Pay Widget also supports off-ramp transactions, allowing users to convert their crypto back into traditional fiat currency. Here’s how the off-ramp process works for end users:

1. **Customer Selection**: The customer selects the amount of crypto they want to convert to fiat.
2. **Wallet Details**: The customer provides their wallet details for receiving the crypto.
3. **Funds Transfer**: The customer transfers the crypto to an agent and confirms the order.
4. **Order Confirmation**: The agent confirms the receipt of crypto, and the system initiates the transfer of fiat currency to the customer's bank account or other specified method.

This process ensures a secure and efficient way for users to convert their crypto holdings into fiat currency.

### Getting Started

To get started, choose the integration type that best suits your needs and follow the detailed guides provided in this documentation. Whether you are setting up a simple donation link or a complex merchant integration, the Fonbnk Pay Widget offers a flexible solution for accepting and converting crypto payments.

Explore the documentation to learn more about the features, configurations, and best practices for using the Fonbnk Pay Widget.


# Integration Guide

### Video tutorial <a href="#setting-up-your-sandbox-environment" id="setting-up-your-sandbox-environment"></a>

{% embed url="<https://vimeo.com/1082484387>" %}

### Setting Up Your Sandbox Environment <a href="#setting-up-your-sandbox-environment" id="setting-up-your-sandbox-environment"></a>

To begin integrating with our system, the first step is to register a merchant account in the sandbox environment. Follow this link to initiate the registration process: <https://sandbox-dashboard.fonbnk.com/register-initiate>.<br>

Configuring Webhook Integration

Once you have a sandbox account, navigate to the **Settings** page on the dashboard. Here, you can configure a webhook URL to receive notifications regarding order status changes.

<figure><img src="/files/HGkdovu0ogfn1ylEVHhf" alt=""><figcaption><p>Webhook setup in the merchant dashboard</p></figcaption></figure>

{% hint style="info" %}
Learn more about the webhook structure and signature [here](/v1/on-ramp/webhook).
{% endhint %}

You can also test your webhook integration using the **Simulate the webhook request** feature. Provide a URL and click the **Send Request** button to have the dashboard send a test notification to the specified URL.

<figure><img src="/files/zWWESYU2Ye4c1BMwwPXI" alt=""><figcaption><p>Webhook simulation in the merchant dashboard</p></figcaption></figure>

{% hint style="info" %}
If you want to preview webhook notifications without setting up a server, you can use the [webhooks service](https://webhook.site/).
{% endhint %}

### Generating Payment URLs and Creating Orders <a href="#generating-payment-urls-and-creating-orders" id="generating-payment-urls-and-creating-orders"></a>

To create sandbox orders, utilize the sandbox pay widget, which can be accessed at [Sandbox Pay Widget](https://sandbox-pay.fonbnk.com/). To associate an order with your merchant account, you must include the **source** parameter in the pay widget URL. You can find the **source** parameter value in the **Additional Details** section of the **Settings** page on the dashboard.

<figure><img src="/files/rqB2exCCzhzJKgW0dZxH" alt=""><figcaption><p>Source param in the merchant dashboard</p></figcaption></figure>

Additionally, you must provide a unique **signature** parameter, which is a JWT token (HS256 encryption algorithm) generated using "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow to create more than 1 order using the same signature. During testing, you can generate a JWT signature using this website, <https://jwt.io/>. You can also provide [URL configuration parameters](/v1/on-ramp/url-parameters) in the JWT token payload.\
&#x20;

An example of a token generation in typescript:

```typescript
import * as jsonwebtoken from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';

const token = jsonwebtoken.sign(
    {
      uid: uuid(),
    },
    YOUR_SIGNATURE_SECRET,
    {
      algorithm: 'HS256',
    },
 );
```

With the provided **source** parameter, the pay widget URL will look like this: <https://sandbox-pay.fonbnk.com/?source=bd3X9Cgq&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJlcmcyMmYyZkBAIn0.Z1BB4eiClKH_k18w5I3tMiutuWpPgPb5gI33FrkpJcY>.

To create an order in the sandbox environment, you must use one of the following accounts if you want an order to be automatically confirmed.

<table><thead><tr><th>Country</th><th>Email</th><th width="170">Password</th></tr></thead><tbody><tr><td>Nigeria</td><td>sandbox-NG@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Kenya</td><td>sandbox-KE@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Ghana</td><td>sandbox-GH@fonbnk.com</td><td>ZoA8dA9CXF</td></tr><tr><td>Any supported country</td><td>sandbox-{countryCode}@fonbnk.com</td><td>ZoA8dA9CXF</td></tr></tbody></table>

You can register your email, but orders will be automatically rejected.

Make sure to use the **Login with Password** flow:

<figure><img src="/files/Vet3e1FwiIBMRoDjq6wU" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Warning

During sandbox testing, do not use real money. Simply confirm the order, and it will be marked as paid.
{% endhint %}

If the correct **source** parameter is present in the URL, the order will be displayed in the **Orders** tab of the dashboard:

<figure><img src="/files/SCkoYgsxnt7YU1ifrlAW" alt=""><figcaption><p>Merchant dashboard on-ramp orders list</p></figcaption></figure>

Webhook requests will also be visible in the **Webhooks** tab of the dashboard:

<figure><img src="/files/nJwAESQwcfJpVw5jIwtN" alt=""><figcaption><p>Merchant dashboard on-ramp webhooks</p></figcaption></figure>

### Merchant API <a href="#merchant-api" id="merchant-api"></a>

For those who wish to access pay widget-related data from their back-end, our merchant API is available. You can find the API documentation here.

### Transitioning to Production <a href="#transitioning-to-production" id="transitioning-to-production"></a>

To create a live merchant account, proceed to register it here: <https://dashboard.fonbnk.com/register-initiate>. The live pay widget can be accessed at <https://pay.fonbnk.com/>.&#x20;

{% hint style="warning" %}
After registering, you'll need to contact our support team and complete a KYB process. Thereafter, you'll be able to receive webhooks and preconfigure user wallet addresses.
{% endhint %}


# On-ramp


# How it works

Fonbnk Pay Widget is a secure and efficient way to accept payments in stable coins from customers in an integrated and non-integrated way.

It's a P2P platform that connects people who want to sell mobile money(Airtime, M-PESA, Bank, etc.) with people who would like to buy it.

How it works for end users:

1. A customer selects his funds source (Airtime, M-PESA, Bank, etc.) and the amount of crypto he would like to receive
2. Customer provides his wallet details
3. Customer transfers funds to an agent we found for him and confirms the order
4. An agent confirms the order and the system sends USDC to a customer's wallet

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify amount of USDC/cUSD to buy
Widget->>User: Show the best offer
User->>Widget: Specify wallet details
User->>Widget: Verify email
User->>Widget: Create order
Widget->>User: Provide transfer funds instructions
Note over User: Send funds to an agent
User->>Widget: Confirm that funds are sent
Note over Agent: Check if funds are received
Agent->>Widget: Confirm that funds are received
Widget->>User: Send USDC/cUSD to user wallet" %}

Example of a flow:

Pay Widget supports configuration via [URL parameters](/v1/on-ramp/url-parameters). Merchants can force the widget to use specific wallet address, memo, crypto amount, etc. This allows integrating the widget as a payment system.<br>


# URL Parameters

### List of parameters <a href="#list-of-parameters" id="list-of-parameters"></a>

Here is the list of parameters that can be added to the URL:

| Parameter       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| address         | <p>Address of the wallet you want to receive crypto to<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| network         | Wallet network. Supported values: POLYGON, ETHEREUM, STELLAR, AVALANCHE, SOLANA,  CELO, BASE, LISK                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| asset           | Wallet asset. Supported values: **USDC**, **CUSD**, **USDT**, **USDC\_E, CKES** depending on network. The default value is **USDC** for all networks that support it except CELO, for CELO it's **CUSD**. Supported network/asset pairs: AVALANCHE (USDC), POLYGON (USDC, USDC\_E, USDT), CELO (CUSD, USDC, USDT, CKES), STELLAR (USDC), SOLANA (USDC),  BASE (USDC), ETHEREUM (USDC),  LISK (USDT)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| memo            | Memo for the transaction                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| amount          | If a currency is not provided, it will be an amount of crypto received after fees. If currency is **airtime,** it will be the amount of airtime a user should spend.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| minAmount       | Minimum amount of order in crypto                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| currency        | Currency of the amount. Supported values: **airtime** or **usdc**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| country         | default selected country iso code, example: **KE** for Kenya, **NG** for Nigeria                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| currencyIso     | currency iso code, example: **KES** for Kenya, **NGN** for Nigeria. Acts like a country parameter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| freezeAmount    | Freezes the amount of order for the user, the user will not be able to change it. The amount is required in the URL for this parameter to work.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| freezeWallet    | <p>Freezes the wallet of order for the user, the user will not be able to change it. The wallet is required in the URL for this parameter to work.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| provider        | Default user funds source to select, supported values: **carrier**, **mpesa**, **mobile\_money**, **bank\_transfer**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| carrier         | id of a mobile carrier to select by default                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| callbackUrl     | <p>if present, "Back to website" link will be displayed on the success page. When a user clicks on it, we will redirect him to the provided URL. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p>                                                                                                                                                                                                                                                                                                                        |
| callbackBtnText | Text of the button that is displayed when **callbackUrl** is provided. Default is: "Back to website"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| email           | user's email                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| closeBtn        | text of the button that will be displayed on the success page. If not provided, the button will not be displayed. On click, it will send a *close-iframe* iframe event, so an integrator can close the widget.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| redirectUrl     | <p>if present, user will be redirected to this URL on order fail or success. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>, <strong><code>{status}</code></strong>, <strong><code>{failReason}</code></strong>. <strong><code>{status}</code></strong> placeholder can the next values: <strong><code>success</code></strong> or <strong><code>fail</code></strong>. Fail reason placeholder can the next values: <strong><code>transaction\_failure</code></strong> or <strong><code>agent\_rejected</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p> |
| quoteId         | id of a quote returned from the [price API request](https://docs.fonbnk.com/docs/pay-widget/merchant-api#get-expected-price).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| hideSwitch      | if present, hides the Buy/Sell switch at the top                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

Parameters allowed only for [registered merchants](https://docs.fonbnk.com/docs/pay-widget/use-cases#registered-merchant-integration-with-webhooks):

| Parameter   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| orderParams | This parameter will be sent to a merchant webhook after the success of the crypto transfer.                                                                                                                                                                                                                                                                                                                                                                                    |
| source      | <p>parameter used to match an order to a merchant if the merchant operates by a huge amount of wallets and can't provide them in the merchant dashboard. Merchants should request our support to assign a source to their accounts.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p> |

Here is an example of a URL with parameters:

{% code overflow="wrap" %}

```
https://pay.fonbnk.com?amount=1&network=POLYGON&asset=USDT
```

{% endcode %}

[<br>](https://docs.fonbnk.com/docs/pay-widget/integration-guide)


# Webhook

We can notify a [registered pay widget merchant](/v1/integration-guide) about the statuses of orders associated with him.

We will make a **POST** request to a provided webhook URL with the next **application/json** contents:

**Webhook V1:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-v1)

{% code overflow="wrap" %}

```typescript
type WebhookRequest = {
  "data": {
    "status":
      | "swap_initiated" // user has created an order
      | "swap_expired" // an order has expired
      | "swap_buyer_rejected"  // user has rejected an order
      | "swap_buyer_confirmed" // user has confirmed an order
      | "swap_seller_rejected" // agent has rejected an order, happens when agent don't receive a payment
      | "swap_seller_confirmed" // agent has confirmed an order
      | "pending" // USDC/cUSD transaction is pending
      | "complete" // USDC/cUSD transaction is complete
      | "failed", // USDC/cUSD transaction has failed
    "date": string, // date when event has happened
    "orderId": string, // order id in our system
    "email": string, // customer's email
    "localCurrencyAmount": number, // amount of local currency user paid
    "localCurrencyIsoCode": string, // ISO code of local currency user paid, e.g. KES, NGN etc.
    "countryIsoCode": string, // ISO code of country user paid from, e.g. KE, NG etc.
    "provider": // payment provider user paid with
      | "carrier"
      | "mpesa"
      | "mobile_money"
      | "bank_transfer"
    "amount": number, // amount of USD user received
    "amountCrypto": number, // amount of crypto user received
    "network": // network user received USDC/cUSD on
      | "POLYGON"
      | "ETHEREUM"
      | "STELLAR"
      | "AVALANCHE"
      | "SOLANA"
      | "BASE"
      | "CELO"
      | "LISK",
    "asset": "USDC" | "CUSD" | "USDT" | "USDC_E", // asset user received
    "address": string, // address user received USDC/cUSD on
    "orderParams"?: string // Content of a orderParams query parameter provided to a pay widget URL. It might be useful for matching a merchant system user to an order user.
    "hash"?: string, // transaction hash
    "resumeUrl": string, // URL where user can resume his order, it point either to the transfer instructions page or to the status page
  },
  "hash": string, // SHA256 encrypted request.data string to validate a webhook request
};
```

{% endcode %}

**Webhook V2:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-v2)

Instead of sending hash inside - **WebhookRequest**, we will send it as a request **x-signature** header

```
Request headers:
x-signature: hash (string)
```

**Webhook verification:**[**​**](https://docs.fonbnk.com/docs/pay-widget/webhook#webhook-verification)

We send a hash field in our webhook to protect merchants from fraudulent requests. Each request should be verified by a secret provided in the dashboard.

Here is how it should be checked in pseudocode:

```
request.body.hash === SHA256(stringify(request.body.data), secret)
```

Here is how it should be checked in Node.js:

For Webhook V1 version:

```javascript
import { createHash } from 'crypto';

request.body.hash === createHash('sha256')
   .update(JSON.stringify(request.body.data))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

For Webhook V2 version:

```javascript
import { createHash } from 'crypto';

request 'x-signature' header === createHash('sha256')
   .update(JSON.stringify(request.body))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

{% hint style="info" %}
You can see how to make a signature in multiple programming languages [HERE](/v1/reference/signing-requests#request-examples)
{% endhint %}


# Off-ramp


# How it works

Off-ramp widget allows user to exchange their crypto currency to his country's local currency.

How it works for end users:

1. Customer selects off-ramp type (only bank transfer is supported now) and specifies the amount of crypto he wants to exchange. System displays how much local currency he will receive.
2. Customer verifies his email by entering a code sent to him.
3. Customer provides his account details such as bank account number, bank name, etc.
4. System returns a wallet address where customer should send his crypto.
5. Customer sends crypto to the provided address and provides a transaction hash to the system.
6. System checks if the transaction is received and sends local currency to the customer's account.

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify amount of crypto to exchange
Widget->>User: Show the best offer
User->>Widget: Verify email
User->>Widget: Provide account details
User->>Widget: Create order
Widget->>User: Wallet address to send crypto
Note over User: Send crypto to the wallet
User->>Widget: Send transaction hash
Note over Widget: Check if funds are received
Widget->>User: Send local currency to the user account" %}


# URL Parameters

### Off-ramp URL[​](https://docs.fonbnk.com/docs/offramp/query-params#off-ramp-url) <a href="#off-ramp-url" id="off-ramp-url"></a>

| Environment | URL                                      |
| ----------- | ---------------------------------------- |
| Sandbox     | <https://sandbox-pay.fonbnk.com/offramp> |
| Production  | <https://pay.fonbnk.com/offramp>         |

### List of parameters[​](https://docs.fonbnk.com/docs/offramp/query-params#list-of-parameters) <a href="#list-of-parameters" id="list-of-parameters"></a>

Here is the list of parameters that can be added to the URL:

| Parameter       | Description                                                                                                                                                                                                                         |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| network         | Wallet network from which user will send crypto. Supported values: **POLYGON**, **AVALANCHE**, **CELO**, **ETHEREUM**                                                                                                               |
| asset           | Wallet asset from which user will send crypto. Supported values: **USDC**,**CUSD** depending on network. Supported network/asset pairs: AVALANCHE (USDC, USDT), POLYGON (USDC, USDT), CELO (CUSD, USDT, USDC), ETHEREUM(USDC, USDT) |
| amount          | If a currency is not provided, it will be an amount of crypto user wants to exchange. If currency is **local** it will be the amount of local currency user wants to receive.                                                       |
| offrampCurrency | Currency of the amount. Supported values: **usd** or **local**                                                                                                                                                                      |
| offrampType     | Type of the off-ramp: **bank**, **airtime**, **mobile\_money**, **paybill**                                                                                                                                                         |
| country         | default selected country iso code, example: **KE** for Kenya, **NG** for Nigeria                                                                                                                                                    |
| currencyIso     | currency iso code, example: **KES** for Kenya, **NGN** for Nigeria. Acts like a country parameter.                                                                                                                                  |
| freezeAmount    | Freezes the amount of order for the user, the user will not be able to change it. The amount is required in the URL for this parameter to work.                                                                                     |
| freezeWallet    | Freezes the wallet of order for the user, the user will not be able to change it. The wallet is required in the URL for this parameter to work.                                                                                     |
| orderParams     | This parameter will be sent to a merchant webhook with order status changes                                                                                                                                                         |
| source          | parameter used to match an order to a merchant                                                                                                                                                                                      |
| hideSwitch      | if present, hides Buy/Sell switch at the top                                                                                                                                                                                        |


# Webhook

We can notify a merchant about the statuses of off-ramp orders associated with him.

We will make a **POST** request to a provided webhook URL with the next **application/json** contents:

**Webhook V1:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-v1)

<pre class="language-typescript"><code class="lang-typescript">type WebhookRequest = {
  data: {
    orderId: string,
    offrampType: "bank",
    status: OfframpStatus,
    date: string,
    cashout: {
      localCurrencyAmount: number, // how much the user will receive in local currency
      usdAmount: number, // how much user must send in USD
      feeAmountUsd: number, // total fee amount in USD
      feeAmountUsdFonbnk: number, // fee amount in USD for Fonbnk
      feeAmountUsdPartner: number, // fee amount in USD for partner
      feeAmountLocalCurrency: number, // total fee amount in local currency
      feeAmountLocalCurrencyFonbnk: number, // fee amount in local currency for Fonbnk
      feeAmountLocalCurrencyPartner: number, // fee amount in local currency for partner
    },
    exchangeRate: number,
    network: "AVALANCHE" | "POLYGON" | "CELO",
    asset: "USDC" | "CUSD",
    fromAddress: string,
    toAddress: string,
    userEmail: string,
    requiredFields: { label: string, type: 'number' | 'string' | 'date' | 'boolean' | 'email' | 'phone', value: string }[],// user account data
    orderParams?: string, // contents of orderParams query parameter during order creation
    countryIsoCode: string,
    currencyIsoCode: string,
  },
  hash: string,
};

enum OfframpStatus  {
  INITIATED = 'initiated', 
  VALIDATING_TRANSACTION = 'validating_transaction', // user has sent us a transaction hash, waiting it to appear in a blockchain
  TRANSACTION_INVALID = 'transaction_invalid', // submited transaction hash is invalid (wrong amount, wrong creation time etc.)
  AWAITING_TRANSACTION_CONFIRMATION = 'awaiting_transaction_confirmation', //waiting for transaction confirmation
  TRANSACTION_CONFIRMED = 'transaction_confirmed', // user transaction was confirmed
  TRANSACTION_FAILED = 'transaction_failed', // user transaction is not confirmed in the blockchain
  OFFRAMP_SUCCESS = 'offramp_success',  // user has received the funds
<strong>  OFFRAMP_RETRY = "offramp_retry", // we are retrying the off-ramp after a failed attempt
</strong>  TRANSACTION_FAILED = 'transaction_failed', // user transaction failed
  OFFRAMP_PENDING = 'offramp_pending', // offramp in progress
  OFFRAMP_FAILED = 'offramp_failed', // offramp failed
  REFUNDING = 'refunding', // offramp failed, refund in progress
  REFUNDED = 'refunded', // offramp failed, refund was successful
  REFUND_FAILED = 'refund_failed', // offramp failed, refund failed
  EXPIRED = 'expired', // user did not send us a transaction hash in time
  CANCELLED = "cancelled" // user cancelled an order
}
</code></pre>

**Webhook V2:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-v2)

Instead of sending hash inside - **WebhookRequest**, we will send it as a request **x-signature** header

```
Request headers:
x-signature: hash (string)
```

**Webhook verification:**[**​**](https://docs.fonbnk.com/docs/offramp/webhook#webhook-verification)

We send a hash field in our webhook to protect merchants from fraudulent requests. Each request should be verified by a secret provided in the dashboard.

Here is how it should be checked in pseudocode:

```
request.body.hash === SHA256(stringify(request.body.data), secret)
```

Here is how it should be checked in Node.js:

For Webhook V1 version:

```javascript
import { createHash } from 'crypto';

request.body.hash === createHash('sha256')
   .update(JSON.stringify(request.body.data))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

For Webhook V2 version:

```javascript
import { createHash } from 'crypto';

request 'x-signature' header === createHash('sha256')
   .update(JSON.stringify(request.body))
   .update(createHash('sha256').update(__SECRET__, 'utf8').digest('hex'))
   .digest('hex');
```

{% hint style="info" %}
You can see how to make a signature in multiple programming languages [HERE](/v1/reference/signing-requests#request-examples)
{% endhint %}


# Servers

### API servers[​](https://docs.fonbnk.com/docs/offramp/merchant-api#api-servers) <a href="#api-servers" id="api-servers"></a>

| Environment | Server URL                                                            |
| ----------- | --------------------------------------------------------------------- |
| Sandbox     | [https://sandbox-api.fonbnk.com](https://sandbox-api.fonbnk.com/)     |
| Production  | [https://aten.fonbnk-services.com](https://aten.fonbnk-services.com/) |


# Signing requests

### Request Authentication[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#request-authentication) <a href="#request-authentication" id="request-authentication"></a>

All requests should be signed using a HMAC256 algorithm and provided `clientId` and `clientSecret`.

### How to get the signature of the request?[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#how-to-get-the-signature-of-the-request) <a href="#how-to-get-the-signature-of-the-request" id="how-to-get-the-signature-of-the-request"></a>

1. Generate a timestamp (Epoch Unix Timestamp) in milliseconds
2. Concatenate the timestamp and the endpoint that is called `{timestamp}:{endpoint}`
3. Decode the base64 encoded clientSecret
4. Compute the SHA256 hash of the concatenated string. Use decoded clientSecret as a key. Convert the result to base64
5. Add the clientId, signature, and timestamp to HTTP headers

The following pseudocode example demonstrates and explains how to sign a request

{% code overflow="wrap" %}

```
timestamp = CurrentTimestamp();
stringToSign = timestamp + ":" + endpoint;
signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( clientSecret ), UTF8 ( concatenatedString ) ) );
```

{% endcode %}

## Request examples <a href="#request-examples" id="request-examples"></a>

The following examples send HTTP request to [get price](broken://pages/O9Fcrf8qVbJBqoo8JVhk)  on-ramp API endpoint:

{% tabs %}
{% tab title="Typescript" %}
{% code overflow="wrap" %}

```typescript
import crypto from 'crypto';
const BASE_URL = 'https://aten.fonbnk-services.com';
const ENDPOINT = '/api/pay-widget-merchant/price';
const CLIENT_ID = '';
const CLIENT_SECRET = '';

const generateSignature = ({
  clientSecret,
  timestamp,
  endpoint,
}: {
  clientSecret: string;
  timestamp: string;
  endpoint: string;
}) => {
  let hmac = crypto.createHmac('sha256', Buffer.from(clientSecret, 'base64'));
  let stringToSign = `${timestamp}:${endpoint}`;
  hmac.update(stringToSign);
  return hmac.digest('base64');
};

const main = async () => {
  const timestamp = new Date().getTime();
  const queryParams = new URLSearchParams({
    country: 'NG',
    amount: '10',
    currency: 'usdc',
    network: 'CELO',
    asset: 'CUSD',
    provider: 'bank_transfer',
  });
  const endpoint = `${ENDPOINT}?${queryParams.toString()}`;
  const signature = generateSignature({
    clientSecret: CLIENT_SECRET,
    timestamp: timestamp.toString(),
    endpoint,
  });
  const headers = {
    'Content-Type': 'application/json',
    'x-client-id': CLIENT_ID,
    'x-timestamp': timestamp.toString(),
    'x-signature': signature,
  };
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    method: 'GET',
    headers,
  });
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
};

main().catch(console.error);

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```python
import hmac
import base64
import time
import requests
from urllib.parse import urlencode

BASE_URL = 'https://aten.fonbnk-services.com'
ENDPOINT = '/api/pay-widget-merchant/price'
CLIENT_ID = ''
CLIENT_SECRET = ''

def pad_base64(base64_string):
    return base64_string + '=' * (-len(base64_string) % 4)

def generate_signature(client_secret, timestamp, endpoint):
    client_secret_padded = pad_base64(client_secret)
    hmac_obj = hmac.new(base64.b64decode(client_secret_padded), f'{timestamp}:{endpoint}'.encode('utf-8'), 'sha256')
    return base64.b64encode(hmac_obj.digest()).decode('utf-8')

def main():
    timestamp = str(int(time.time() * 1000))
    query_params = {
        'country': 'NG',
        'amount': '10',
        'currency': 'usdc',
        'network': 'CELO',
        'asset': 'CUSD',
        'provider': 'bank_transfer',
    }
    endpoint = f"{ENDPOINT}?{urlencode(query_params)}"
    signature = generate_signature(CLIENT_SECRET, timestamp, endpoint)
    headers = {
        'Content-Type': 'application/json',
        'x-client-id': CLIENT_ID,
        'x-timestamp': timestamp,
        'x-signature': signature,
    }
    response = requests.get(f"{BASE_URL}{endpoint}", headers=headers)
    data = response.json()
    print(data)

if __name__ == "__main__":
    main()

```

{% endcode %}
{% endtab %}

{% tab title="GO" %}
{% code overflow="wrap" %}

```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	BASE_URL      = "https://aten.fonbnk-services.com"
	ENDPOINT      = "/api/pay-widget-merchant/price"
	CLIENT_ID     = ""
	CLIENT_SECRET = ""
)

func padBase64(base64String string) string {
	return base64String + strings.Repeat("=", (4-len(base64String)%4)%4)
}

func generateSignature(clientSecret, timestamp, endpoint string) (string, error) {
	clientSecretPadded := padBase64(clientSecret)
	decodedSecret, err := base64.StdEncoding.DecodeString(clientSecretPadded)
	if err != nil {
		return "", err
	}
	message := fmt.Sprintf("%s:%s", timestamp, endpoint)
	h := hmac.New(sha256.New, decodedSecret)
	h.Write([]byte(message))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

func main() {
	timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
	queryParams := url.Values{
		"country":  {"NG"},
		"amount":   {"10"},
		"currency": {"usdc"},
		"network":  {"CELO"},
		"asset":    {"CUSD"},
		"provider": {"bank_transfer"},
	}
	endpoint := fmt.Sprintf("%s?%s", ENDPOINT, queryParams.Encode())
	signature, err := generateSignature(CLIENT_SECRET, timestamp, endpoint)
	if err != nil {
		fmt.Println("Error generating signature:", err)
		return
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", BASE_URL+endpoint, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-client-id", CLIENT_ID)
	req.Header.Set("x-timestamp", timestamp)
	req.Header.Set("x-signature", signature)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making request:", err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	var data map[string]interface{}
	if err := json.Unmarshal(body, &data); err != nil {
		fmt.Println("Error unmarshalling response:", err)
		return
	}

	fmt.Println(data)
}

```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code overflow="wrap" %}

```php
<?php

define('BASE_URL', 'https://aten.fonbnk-services.com');
define('ENDPOINT', '/api/pay-widget-merchant/price');
define('CLIENT_ID', '');
define('CLIENT_SECRET', '');

function pad_base64($base64_string) {
    return $base64_string . str_repeat('=', (4 - strlen($base64_string) % 4) % 4);
}

function generate_signature($client_secret, $timestamp, $endpoint) {
    $client_secret_padded = pad_base64($client_secret);
    $hmac = hash_hmac('sha256', "$timestamp:$endpoint", base64_decode($client_secret_padded), true);
    return base64_encode($hmac);
}

function main() {
    $timestamp = (string) round(microtime(true) * 1000);
    $query_params = [
        'country' => 'NG',
        'amount' => '10',
        'currency' => 'usdc',
        'network' => 'CELO',
        'asset' => 'CUSD',
        'provider' => 'bank_transfer',
    ];
    $endpoint = ENDPOINT . '?' . http_build_query($query_params);
    $signature = generate_signature(CLIENT_SECRET, $timestamp, $endpoint);
    $headers = [
        'Content-Type: application/json',
        'x-client-id: ' . CLIENT_ID,
        'x-timestamp: ' . $timestamp,
        'x-signature: ' . $signature,
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, BASE_URL . $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    print_r($data);
}

main();
?>
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    private static final String BASE_URL = "https://aten.fonbnk-services.com";
    private static final String ENDPOINT = "/api/pay-widget-merchant/price";
    private static final String CLIENT_ID = "";
    private static final String CLIENT_SECRET = "";

    public static void main(String[] args) throws Exception {
        long timestamp = System.currentTimeMillis();
        Map<String, String> queryParams = new HashMap<>();
        queryParams.put("country", "NG");
        queryParams.put("amount", "10");
        queryParams.put("currency", "usdc");
        queryParams.put("network", "CELO");
        queryParams.put("asset", "CUSD");
        queryParams.put("provider", "bank_transfer");

        String endpoint = ENDPOINT + "?" + getQuery(queryParams);
        String signature = generateSignature(CLIENT_SECRET, String.valueOf(timestamp), endpoint);

        URL url = new URL(BASE_URL + endpoint);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("x-client-id", CLIENT_ID);
        connection.setRequestProperty("x-timestamp", String.valueOf(timestamp));
        connection.setRequestProperty("x-signature", signature);

        Scanner scanner = new Scanner(connection.getInputStream());
        String response = scanner.useDelimiter("\\A").next();
        System.out.println(response);
        scanner.close();
    }

    private static String padBase64(String base64String) {
        return base64String + "=".repeat((4 - base64String.length() % 4) % 4);
    }

    private static String generateSignature(String clientSecret, String timestamp, String endpoint) throws Exception {
        String clientSecretPadded = padBase64(clientSecret);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(clientSecretPadded), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        String data = timestamp + ":" + endpoint;
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hmacBytes);
    }

    private static String getQuery(Map<String, String> params) throws Exception {
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (result.length() > 0) {
                result.append("&");
            }
            result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            result.append("=");
            result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }
        return result.toString();
    }
}
```

{% endtab %}

{% tab title="Dart" %}

```dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

void main() async {
  const String BASE_URL = "https://aten.fonbnk-services.com";
  const String ENDPOINT = "/api/pay-widget-merchant/price";
  const String CLIENT_ID = "";
  const String CLIENT_SECRET = "";

  // Get the current timestamp in milliseconds
  int timestamp = DateTime.now().millisecondsSinceEpoch;

  // Create query parameters
  Map<String, String> queryParams = {
    "country": "NG",
    "amount": "10",
    "currency": "usdc",
    "network": "CELO",
    "asset": "CUSD",
    "provider": "bank_transfer",
  };

  // Generate the query string
  String queryString = getQuery(queryParams);

  // Create the endpoint with query parameters
  String endpoint = ENDPOINT + "?" + queryString;

  // Generate the signature
  String signature = generateSignature(CLIENT_SECRET, timestamp.toString(), endpoint);

  // Build the URL
  String url = BASE_URL + endpoint;

  // Set up the HTTP GET request
  var headers = {
    "Content-Type": "application/json",
    "x-client-id": CLIENT_ID,
    "x-timestamp": timestamp.toString(),
    "x-signature": signature,
  };

  // Send the GET request
  var response = await http.get(Uri.parse(url), headers: headers);

  // Print the response body
  print(response.body);
}

String getQuery(Map<String, String> params) {
  return params.entries
      .map((entry) =>
  Uri.encodeQueryComponent(entry.key) + "=" + Uri.encodeQueryComponent(entry.value))
      .join("&");
}

String generateSignature(String clientSecret, String timestamp, String endpoint) {
  // Use the custom lenient Base64 decoder
  List<int> secretKey = lenientBase64Decode(clientSecret);

  Hmac hmac = Hmac(sha256, secretKey);
  String data = '$timestamp:$endpoint';
  Digest digest = hmac.convert(utf8.encode(data));

  // Encode the signature using Base64
  String signature = base64Encode(digest.bytes);
  return signature;
}

List<int> lenientBase64Decode(String input) {
  // Base64 index table
  const String base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  // Remove all characters that are not in the Base64 alphabet
  String sanitizedInput = input.replaceAll(RegExp(r'[^A-Za-z0-9+/]'), '');

  // Map each character to its Base64 index
  List<int> buffer = [];
  int bits = 0;
  int bitsCount = 0;

  for (int i = 0; i < sanitizedInput.length; i++) {
    int val = base64Chars.indexOf(sanitizedInput[i]);
    if (val < 0) {
      // Skip invalid characters
      continue;
    }
    bits = (bits << 6) | val;
    bitsCount += 6;
    if (bitsCount >= 8) {
      bitsCount -= 8;
      int byte = (bits >> bitsCount) & 0xFF;
      buffer.add(byte);
    }
  }

  return buffer;
}
```

{% endtab %}

{% tab title="Elixir" %}

```elixir
Mix.install([
  {:httpoison, "~> 1.8"},
  {:jason, "~> 1.4"}
])

defmodule FonbnkClient do
  @moduledoc """
  A client for interacting with the Fonbnk API.
  """

  @base_url "https://aten.fonbnk-services.com"
  @endpoint "/api/pay-widget-merchant/price"
  @client_id ""
  @client_secret ""

  def pad_base64(base64_string) do
    pad_length = Integer.mod(-String.length(base64_string), 4)
    base64_string <> String.duplicate("=", pad_length)
  end

  def generate_signature(client_secret, timestamp, endpoint) do
    client_secret_padded = pad_base64(client_secret)
    {:ok, client_secret_decoded} = Base.decode64(client_secret_padded)
    message = "#{timestamp}:#{endpoint}"
    hmac = :crypto.mac(:hmac, :sha256, client_secret_decoded, message)
    Base.encode64(hmac)
  end

  def main do
    timestamp = :os.system_time(:millisecond) |> Integer.to_string()
    query_params = %{
      "country" => "NG",
      "amount" => "10",
      "currency" => "usdc",
      "network" => "CELO",
      "asset" => "CUSD",
      "provider" => "bank_transfer"
    }

    encoded_query = URI.encode_query(query_params)
    endpoint = @endpoint <> "?" <> encoded_query
    signature = generate_signature(@client_secret, timestamp, endpoint)

    headers = [
      {"Content-Type", "application/json"},
      {"x-client-id", @client_id},
      {"x-timestamp", timestamp},
      {"x-signature", signature}
    ]

    url = @base_url <> endpoint

    case HTTPoison.get(url, headers) do
      {:ok, %HTTPoison.Response{body: body, status_code: code}} when code in 200..299 ->
        data = Jason.decode!(body)
        IO.inspect(data)

      {:ok, %HTTPoison.Response{body: body, status_code: code}} ->
        IO.puts("HTTP Error #{code}: #{body}")

      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts("Request Error: #{inspect(reason)}")
    end
  end
end

FonbnkClient.main()
```

{% endtab %}
{% endtabs %}


# Endpoints


# On Ramp

On-ramp

## Get list of supported assets

> Returns a list of supported blockchain assets for the on-ramp

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]}}},"paths":{"/api/pay-widget-merchant/assets":{"get":{"tags":["on-ramp"],"summary":"Get list of supported assets","description":"Returns a list of supported blockchain assets for the on-ramp","operationId":"getOnrampAssets","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OnrampNetwork"},"asset":{"$ref":"#/components/schemas/OnrampAsset"}}}}}}}}}}}}
```

## Get order

> Returns a single pay widget order by its ID or orderParams query parameter.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"walletType":{"$ref":"#/components/schemas/OnrampNetwork","description":"Network type"},"asset":{"$ref":"#/components/schemas/OnrampAsset","description":"Asset type"},"walletAddress":{"type":"string","description":"User wallet address"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasUsdAmount":{"type":"number"},"merchantId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"buySwap":{"type":"object","properties":{"_id":{"type":"string"},"buyerUserPhone":{"type":"string"},"buyerUserEmail":{"type":"string"},"sellerUserPhone":{"type":"string"},"amount":{"type":"number","description":"Amount in cents"},"airtimeAmount":{"type":"number"},"status":{"$ref":"#/components/schemas/BuySwapStatus"},"provider":{"$ref":"#/components/schemas/OnRampProvider"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"buyerConfirmedAt":{"type":"string","format":"date-time"},"countryIsoCode":{"type":"string"}}},"withdrawal":{"type":"object","properties":{"_id":{"type":"string"},"status":{"$ref":"#/components/schemas/WithdrawalStatus"},"withdrawAmount":{"description":"Amount in USD","type":"number"},"withdrawCryptoAmount":{"description":"Amount in crypto","type":"number"},"transactionHash":{"type":"string"}}},"feeAmount":{"type":"number","description":"Total fee amount in USD"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"resumeUrl":{"type":"string"}}},"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]},"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]}}},"paths":{"/api/pay-widget-merchant/order":{"get":{"tags":["on-ramp"],"summary":"Get order","description":"Returns a single pay widget order by its ID or orderParams query parameter.","operationId":"getOnrampOrderById","parameters":[{"name":"orderId","in":"query","description":"id of the order which you could receive via a webhook or iframe events","required":false,"schema":{"type":"string"}},{"name":"orderParams","in":"query","required":false,"description":"Value which you provided in the orderParams parameter of the pay widget URL","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnrampOrder"}}}}}}}}}
```

## Get orders

> Returns a paginated list of pay widget orders. Filters can be applied to the list by providing query parameters.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"},"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]},"PaginatedOnrampOrders":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OnrampOrder"}}}}]},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OnrampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"walletType":{"$ref":"#/components/schemas/OnrampNetwork","description":"Network type"},"asset":{"$ref":"#/components/schemas/OnrampAsset","description":"Asset type"},"walletAddress":{"type":"string","description":"User wallet address"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasUsdAmount":{"type":"number"},"merchantId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"buySwap":{"type":"object","properties":{"_id":{"type":"string"},"buyerUserPhone":{"type":"string"},"buyerUserEmail":{"type":"string"},"sellerUserPhone":{"type":"string"},"amount":{"type":"number","description":"Amount in cents"},"airtimeAmount":{"type":"number"},"status":{"$ref":"#/components/schemas/BuySwapStatus"},"provider":{"$ref":"#/components/schemas/OnRampProvider"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"buyerConfirmedAt":{"type":"string","format":"date-time"},"countryIsoCode":{"type":"string"}}},"withdrawal":{"type":"object","properties":{"_id":{"type":"string"},"status":{"$ref":"#/components/schemas/WithdrawalStatus"},"withdrawAmount":{"description":"Amount in USD","type":"number"},"withdrawCryptoAmount":{"description":"Amount in crypto","type":"number"},"transactionHash":{"type":"string"}}},"feeAmount":{"type":"number","description":"Total fee amount in USD"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"resumeUrl":{"type":"string"}}},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]}}},"paths":{"/api/pay-widget-merchant/orders":{"get":{"tags":["on-ramp"],"summary":"Get orders","description":"Returns a paginated list of pay widget orders. Filters can be applied to the list by providing query parameters.","operationId":"getOnrampOrders","parameters":[{"name":"cursor","in":"query","description":"this parameter should be provided in order to get a next page from the pagination, it should be taken from \"nextCursor\" response value","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"number from 1 to 100, describes how many records should be in each pagination page","required":true,"schema":{"type":"integer"}},{"name":"walletType","in":"query","description":"wallet type of orders","required":false,"schema":{"$ref":"#/components/schemas/OnrampNetwork"}},{"name":"walletAddress","in":"query","required":false,"schema":{"type":"string"}},{"name":"userPhoneNumber","in":"query","description":"phone number of the client, should include country code","required":false,"schema":{"type":"string"}},{"name":"userEmail","in":"query","description":"email of the client","required":false,"schema":{"type":"string"}},{"name":"swapProvider","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OnRampProvider"}},{"name":"buySwapStatus","in":"query","description":"status of a buy swap","required":false,"schema":{"$ref":"#/components/schemas/BuySwapStatus"}},{"name":"withdrawalStatus","in":"query","description":"status of a crypto transfer","required":false,"schema":{"$ref":"#/components/schemas/WithdrawalStatus"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedOnrampOrders"}}}}}}}}}
```

## Get price

> Returns expected price in USDC, cUSD etc. for a given amount of mobile money and vice versa.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]},"OnrampCurrency":{"type":"string","enum":["usdc","local"]},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"OnrampPriceResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"cryptoTotalAmount":{"type":"number","description":"Amount of crypto user should receive before fees"},"cryptoWithdrawAmount":{"type":"number","description":"Amount of crypto user should receive after fees"},"cryptoFeeAmount":{"type":"number","description":"Total fee amount (fonbnk fee + partner fee) in crypto"},"cryptoGasAmount":{"type":"number","description":"Network fee in crypto"},"localCurrencyAmount":{"type":"number","description":"Amount of local currency user should pay"},"feePercent":{"type":"number","description":"Total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"totalAmount":{"type":"number","description":"Amount of funds user will receive before fees"},"withdrawAmount":{"type":"number","description":"Amount of funds user will receive after fees"},"feeAmount":{"type":"number","description":"Total fee amount (fonbnk fee + partner fee)"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency (fonbnk fee + partner fee)"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"usdcTotalAmount":{"type":"number","description":"Amount of usd user will receive before fees (deprecated)"},"usdcWithdrawAmount":{"type":"number","description":"Amount of usd user will receive after fees (deprecated)"},"usdcFeeAmount":{"type":"number","description":"Fonbnk service fee (deprecated)"},"usdcGasAmount":{"type":"number","description":"Network fee (deprecated)"}}}}},"paths":{"/api/pay-widget-merchant/price":{"get":{"tags":["on-ramp"],"summary":"Get price","description":"Returns expected price in USDC, cUSD etc. for a given amount of mobile money and vice versa.","operationId":"getPrice","parameters":[{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnrampNetwork"}},{"name":"asset","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnrampAsset"}},{"name":"currency","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnrampCurrency"}},{"name":"amount","in":"query","required":true,"schema":{"type":"number"}},{"name":"country","in":"query","required":true,"description":"country ISO code, e.g. NG","schema":{"type":"string"}},{"name":"provider","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnRampProvider"}},{"name":"carrierId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnrampPriceResponse"}}}}}}}}}
```

## Get providers

> Returns a list of providers. Optionally includes limitations data.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampProvidersListResponse":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"providers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"requiresCarrier":{"type":"boolean"},"limits":{"$ref":"#/components/schemas/OnrampProviderLimitations"},"carriers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"limits":{"$ref":"#/components/schemas/OnrampProviderLimitations"}}}}}}}}}},"OnrampProviderLimitations":{"type":"object","properties":{"AVALANCHE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"CELO":{"$ref":"#/components/schemas/OnrampProviderLimit"},"POLYGON":{"$ref":"#/components/schemas/OnrampProviderLimit"},"STELLAR":{"$ref":"#/components/schemas/OnrampProviderLimit"},"SOLANA":{"$ref":"#/components/schemas/OnrampProviderLimit"},"BASE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"TON":{"$ref":"#/components/schemas/OnrampProviderLimit"}}},"OnrampProviderLimit":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}},"localCurrency":{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"max":{"type":"number"},"min":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}}}}}},"paths":{"/api/pay-widget-merchant/providers":{"get":{"tags":["on-ramp"],"summary":"Get providers","description":"Returns a list of providers. Optionally includes limitations data.","operationId":"getProviders","parameters":[{"name":"includeLimits","in":"query","description":"Should limitations data be included in the response. Defaults to true. If limitations are not included the request will be much faster.","required":false,"schema":{"type":"boolean","default":true}},{"name":"network","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OnrampNetwork"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnrampProvidersListResponse"}}}}}}}}}
```

## Get limits

> Returns minimum and maximum amount of order in crypto and local currency and applied fees.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"on-ramp","description":"On-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"OnrampLimitsResponse":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"localCurrencyLimits":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}},{"type":"object","properties":{"type":{"type":"string","enum":["fixed_list"]},"values":{"type":"array","items":{"type":"number"}},"withCents":{"type":"boolean"}}}]},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}}}}}},"paths":{"/api/pay-widget-merchant/limits":{"get":{"tags":["on-ramp"],"summary":"Get limits","description":"Returns minimum and maximum amount of order in crypto and local currency and applied fees.","operationId":"getLimits","parameters":[{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnrampNetwork"}},{"name":"asset","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OnrampAsset"}},{"name":"country","in":"query","required":true,"schema":{"type":"string"}},{"name":"provider","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OnRampProvider"}},{"name":"carrierId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnrampLimitsResponse"}}}}}}}}}
```


# Off Ramp

Off-ramp

## Get off-ramp order

> Returns a single order by its ID.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpOrder":{"type":"object","properties":{"_id":{"type":"string"},"offerId":{"type":"string"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"exchangeRate":{"type":"number"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}},"fromAddress":{"type":"string"},"toAddress":{"type":"string"},"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"hash":{"type":"string"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"changedAt":{"type":"string","format":"date-time"}}}},"requiredFields":{"type":"object","description":"Fields that was required to be filled by the user"},"countryIsoCode":{"type":"string"},"userPhoneNumber":{"type":"string"},"userEmail":{"type":"string"},"currencyIsoCode":{"type":"string"},"offerRequiredFields":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"type":{"type":"string"},"value":{"type":"string"}}}},"orderParams":{"type":"string"}}},"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}},"paths":{"/api/offramp/order/{id}":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp order","description":"Returns a single order by its ID.","operationId":"getOfframpOrderById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpOrder"}}}}}}}}}
```

## Get off-ramp orders

> Returns a paginated list of orders. Filters can be applied to the list by providing query parameters.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OfframpOrder":{"type":"object","properties":{"_id":{"type":"string"},"offerId":{"type":"string"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"exchangeRate":{"type":"number"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}},"fromAddress":{"type":"string"},"toAddress":{"type":"string"},"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"hash":{"type":"string"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"changedAt":{"type":"string","format":"date-time"}}}},"requiredFields":{"type":"object","description":"Fields that was required to be filled by the user"},"countryIsoCode":{"type":"string"},"userPhoneNumber":{"type":"string"},"userEmail":{"type":"string"},"currencyIsoCode":{"type":"string"},"offerRequiredFields":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"type":{"type":"string"},"value":{"type":"string"}}}},"orderParams":{"type":"string"}}}}},"paths":{"/api/offramp/orders":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp orders","description":"Returns a paginated list of orders. Filters can be applied to the list by providing query parameters.","operationId":"getOfframpOrders","parameters":[{"name":"cursor","in":"query","description":"this parameter should be provided in order to get a next page from the pagination, it should be taken from \"nextCursor\" response value","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"number from 1 to 100, describes how many records should be in each pagination page","required":true,"schema":{"type":"integer"}},{"name":"paymentType","in":"query","description":"payment type of orders","required":false,"schema":{"$ref":"#/components/schemas/OfframpPaymentType"}},{"name":"network","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OfframpNetwork"}},{"name":"asset","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OfframpAsset"}},{"name":"fromAddress","in":"query","description":"address of a user wallet","required":false,"schema":{"type":"string"}},{"name":"userPhoneNumber","in":"query","description":"phone number of the client, should include country code","required":false,"schema":{"type":"string"}},{"name":"userEmail","in":"query","description":"email of the client","required":false,"schema":{"type":"string"}},{"name":"hash","in":"query","description":"hash of the user transaction","required":false,"schema":{"type":"string"}},{"name":"countryIsoCode","in":"query","description":"country ISO code, e.g. NG","required":false,"schema":{"type":"string"}},{"name":"offrampType","in":"query","description":"type of the offramp","required":false,"schema":{"$ref":"#/components/schemas/OfframpType"}},{"name":"orderParams","in":"query","description":"value of the orderParams query param during order creation","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"status of the order","required":false,"schema":{"$ref":"#/components/schemas/OfframpOrderStatus"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OfframpOrder"}}}}]}}}}}}}}}
```

## Get best offer

> Returns the best offer for the provided country, network, asset, amount and off-ramp type.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpAmountCurrency":{"type":"string","enum":["local","usd"]},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"BestOfframpOfferResponse":{"type":"object","properties":{"offer":{"type":"object","properties":{"_id":{"type":"string"},"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"exchangeRate":{"type":"number"},"cryptoExchangeRate":{"type":"number"},"requiredFields":{"type":"object","properties":{"fieldName":{"type":"object","properties":{"type":{"ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}},"type":{"$ref":"#/components/schemas/OfframpType"}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}}}}}},"paths":{"/api/offramp/best-offer":{"get":{"tags":["off-ramp"],"summary":"Get best offer","description":"Returns the best offer for the provided country, network, asset, amount and off-ramp type.","operationId":"getBestOffer","parameters":[{"name":"amount","in":"query","description":"Amount of usd user wants to pay or amount of local currency user wants to receive depending on the currency param value","required":true,"schema":{"type":"number"}},{"name":"currency","in":"query","description":"Currency of the amount param","required":true,"schema":{"$ref":"#/components/schemas/OfframpAmountCurrency"}},{"name":"country","in":"query","description":"country ISO code, for example KE for Kenya, NG for Nigeria","required":true,"schema":{"type":"string"}},{"name":"type","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfframpType"}},{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfframpNetwork"}},{"name":"asset","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfframpAsset"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BestOfframpOfferResponse"}}}}}}}}}
```

## Get off-ramp limits

> Returns minimum and maximum amount of order in USD and local currency with applied fees.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number"},"maxUsd":{"type":"number"},"minLocalCurrency":{"type":"number"},"maxLocalCurrency":{"type":"number"}}}}},"paths":{"/api/offramp/limits":{"get":{"tags":["off-ramp"],"summary":"Get off-ramp limits","description":"Returns minimum and maximum amount of order in USD and local currency with applied fees.","operationId":"getOfframpLimits","parameters":[{"name":"type","in":"query","description":"offramp type","required":true,"schema":{"$ref":"#/components/schemas/OfframpType"}},{"name":"country","in":"query","description":"country ISO code","required":true,"schema":{"type":"string"}},{"name":"network","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfframpNetwork"}},{"name":"asset","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfframpAsset"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpLimitsResponse"}}}}}}}}}
```

## Get supported countries

> Returns a list of supported countries and their offramp types

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpSupportedCountry":{"type":"object","properties":{"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"name":{"type":"string"},"offrampTypes":{"type":"array","items":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OfframpType"},"name":{"type":"string"},"carriers":{"type":"array","items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}}},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}},"paths":{"/api/offramp/countries":{"get":{"tags":["off-ramp"],"summary":"Get supported countries","description":"Returns a list of supported countries and their offramp types","operationId":"getSupportedCountries","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OfframpSupportedCountry"}}}}}}}}}}
```

## Get supported blockchain assets

> Returns a list of supported wallet networks and their assets for crypto wallet orders

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpWallet":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"}}},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]}}},"paths":{"/api/offramp/wallets":{"get":{"tags":["off-ramp"],"summary":"Get supported blockchain assets","description":"Returns a list of supported wallet networks and their assets for crypto wallet orders","responses":{"200":{"description":"A list of supported wallet networks and their assets","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OfframpWallet"}}}}}}}}}}
```

## Validate user required fields

> The get best offer endpoint returns the required fields that need to be provided by a user. This endpoint allows you to validate the fields provided by a user. Endpoint might return a list of user information that can help a user to verify the correctness of the provided information.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OfframpValidateFieldsRequest":{"type":"object","properties":{"offerId":{"type":"string","description":"ID of the offer returned from get best offer endpoint"},"requiredFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Object with user required fields"}},"required":["offerId","requiredFields"]},"OfframpValidateFieldsResponse":{"type":"object","properties":{"details":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"}}}}}}}},"paths":{"/api/offramp/validate-fields":{"post":{"tags":["off-ramp"],"summary":"Validate user required fields","description":"The get best offer endpoint returns the required fields that need to be provided by a user. This endpoint allows you to validate the fields provided by a user. Endpoint might return a list of user information that can help a user to verify the correctness of the provided information.","operationId":"validateUserRequiredFields","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpValidateFieldsRequest"}}}},"responses":{"200":{"description":"Validation successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpValidateFieldsResponse"}}}}}}}}}
```

## Create order

> Creates an order for a provided user details.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"CreateOrderRequest":{"type":"object","properties":{"offerId":{"type":"string","description":"ID of the offer returned from get best offer endpoint"},"requiredFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Object with user required fields"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"address":{"type":"string","description":"Address of the wallet from which funds will be sent"},"currency":{"$ref":"#/components/schemas/OfframpAmountCurrency"},"amount":{"type":"number","description":"Amount of usd user wants to pay or amount of local currency user wants to receive depending on the currency param value"},"ip":{"type":"string","description":"IP address of a user"},"orderParams":{"type":"string","description":"OrderParams that need to be associated with an order"}},"required":["offerId","requiredFields","paymentType","network","asset","address","currency","amount"]},"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpAmountCurrency":{"type":"string","enum":["local","usd"]},"OfframpOrder":{"type":"object","properties":{"_id":{"type":"string"},"offerId":{"type":"string"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"exchangeRate":{"type":"number"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}},"fromAddress":{"type":"string"},"toAddress":{"type":"string"},"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"hash":{"type":"string"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"changedAt":{"type":"string","format":"date-time"}}}},"requiredFields":{"type":"object","description":"Fields that was required to be filled by the user"},"countryIsoCode":{"type":"string"},"userPhoneNumber":{"type":"string"},"userEmail":{"type":"string"},"currencyIsoCode":{"type":"string"},"offerRequiredFields":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"type":{"type":"string"},"value":{"type":"string"}}}},"orderParams":{"type":"string"}}},"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}},"paths":{"/api/offramp/create-order":{"post":{"tags":["off-ramp"],"summary":"Create order","description":"Creates an order for a provided user details.","operationId":"createOfframpOrder","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderRequest"}}}},"responses":{"200":{"description":"Order created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpOrder"}}}}}}}}}
```

## Confirm order

> Confirms an order by providing a transaction hash for crypto orders and order ID returned from the create order endpoint.

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"off-ramp","description":"Off-ramp"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"ConfirmOrderRequest":{"type":"object","properties":{"orderId":{"type":"string","description":"ID of the order returned from create order endpoint"},"hash":{"type":"string","description":"Transaction hash for crypto orders"}},"required":["orderId","hash"]},"OfframpOrder":{"type":"object","properties":{"_id":{"type":"string"},"offerId":{"type":"string"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"exchangeRate":{"type":"number"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}},"fromAddress":{"type":"string"},"toAddress":{"type":"string"},"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"hash":{"type":"string"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"changedAt":{"type":"string","format":"date-time"}}}},"requiredFields":{"type":"object","description":"Fields that was required to be filled by the user"},"countryIsoCode":{"type":"string"},"userPhoneNumber":{"type":"string"},"userEmail":{"type":"string"},"currencyIsoCode":{"type":"string"},"offerRequiredFields":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"type":{"type":"string"},"value":{"type":"string"}}}},"orderParams":{"type":"string"}}},"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}},"paths":{"/api/offramp/confirm-order":{"post":{"tags":["off-ramp"],"summary":"Confirm order","description":"Confirms an order by providing a transaction hash for crypto orders and order ID returned from the create order endpoint.","operationId":"confirmOfframpOrder","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmOrderRequest"}}}},"responses":{"200":{"description":"Order confirmed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfframpOrder"}}}}}}}}}
```


# Util

Utility

## Check address

> Check if the provided wallet address was used in the Fonbnk system

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}}},"paths":{"/api/util/check-address":{"post":{"tags":["util"],"summary":"Check address","description":"Check if the provided wallet address was used in the Fonbnk system","operationId":"checkAddress","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string"}}}}}},"responses":{"200":{"description":"Address usage status","content":{"application/json":{"schema":{"type":"object","properties":{"used":{"type":"boolean"}}}}}}}}}}}
```

## Get supported blockchain assets

> Returns a list of supported blockchain assets for the off-ramp and on-ramp

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"util","description":"Utility"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]}}},"paths":{"/api/util/assets":{"get":{"tags":["util"],"summary":"Get supported blockchain assets","description":"Returns a list of supported blockchain assets for the off-ramp and on-ramp","operationId":"blockchainAssets","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OnrampNetwork"},"asset":{"$ref":"#/components/schemas/OnrampAsset"},"canOnramp":{"type":"boolean"},"canOfframp":{"type":"boolean"}}}}}}}}}}}}
```


# Kyc

KYC

## Get KYC state

> Returns kyc state of the user with the provided phone number, also returns supported documents for KYC submission

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"tags":[{"name":"kyc","description":"KYC"}],"servers":[{"url":"https://sandbox-api.fonbnk.com","description":"Development server"},{"url":"https://aten.fonbnk-services.com","description":"Production server"}],"security":[{"ClientIdHeader":[]},{"TimestampHeader":[]},{"SignatureHeader":[]}],"components":{"securitySchemes":{"ClientIdHeader":{"type":"apiKey","in":"header","name":"x-client-id"},"TimestampHeader":{"type":"apiKey","in":"header","name":"x-timestamp"},"SignatureHeader":{"type":"apiKey","in":"header","name":"x-signature"}},"schemas":{"KycStateResponse":{"type":"object","properties":{"kycUrl":{"type":"string","description":"URL to the KYC form"},"offrampKycRules":{"type":"array","items":{"description":"Type of the off-ramp","type":{"$ref":"#/components/schemas/OfframpType"}},"minAmount":{"type":"number","description":"Minimum amount of order in USD for KYC to be required"}},"onrampKycRules":{"type":"array","items":{"description":"Type of the on-ramp","type":{"$ref":"#/components/schemas/OnRampProvider"}},"minAmount":{"type":"number","description":"Minimum amount of order in USD for KYC to be required"}},"passedKyc":{"type":"boolean","description":"Indicates if the user has passed KYC"},"kycStatus":{"type":"enum","description":"Status of the last KYC","enum":["initiated","approved","rejected","invalid"],"kycStatusDescription":{"type":"string","description":"Description of the last KYC status"},"reachedKycLimit":{"type":"boolean","description":"Indicates if the user has reached the KYC limit"},"documentTypes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Title of the document"},"value":{"type":"string","description":"ID of the document"},"requiredFields":{"type":"object","description":"Required fields for the document","properties":{"fieldName":{"type":"object","properties":{"type":{"ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}},"format":{"type":"string","description":"Format of the field, should be used as an input placeholder"},"regexp":{"type":"string","description":"Regular expression to validate the field"},"regexpFlags":{"type":"string","description":"Regular expression flags"}}}}}}}}}}},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]}}},"paths":{"/api/kyc/state":{"get":{"tags":["kyc"],"summary":"Get KYC state","description":"Returns kyc state of the user with the provided phone number, also returns supported documents for KYC submission","operationId":"getKycState","parameters":[{"name":"phoneNumber","in":"query","required":false,"schema":{"type":"string"}},{"name":"email","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KycStateResponse"}}}}}}}}}
```


# Models

## The OnrampNetwork object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]}}}}
```

## The OnrampAsset object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]}}}}
```

## The BuySwapStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"}}}}
```

## The OnRampProvider object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]}}}}
```

## The WithdrawalStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]}}}}
```

## The OnrampOrder object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"walletType":{"$ref":"#/components/schemas/OnrampNetwork","description":"Network type"},"asset":{"$ref":"#/components/schemas/OnrampAsset","description":"Asset type"},"walletAddress":{"type":"string","description":"User wallet address"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasUsdAmount":{"type":"number"},"merchantId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"buySwap":{"type":"object","properties":{"_id":{"type":"string"},"buyerUserPhone":{"type":"string"},"buyerUserEmail":{"type":"string"},"sellerUserPhone":{"type":"string"},"amount":{"type":"number","description":"Amount in cents"},"airtimeAmount":{"type":"number"},"status":{"$ref":"#/components/schemas/BuySwapStatus"},"provider":{"$ref":"#/components/schemas/OnRampProvider"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"buyerConfirmedAt":{"type":"string","format":"date-time"},"countryIsoCode":{"type":"string"}}},"withdrawal":{"type":"object","properties":{"_id":{"type":"string"},"status":{"$ref":"#/components/schemas/WithdrawalStatus"},"withdrawAmount":{"description":"Amount in USD","type":"number"},"withdrawCryptoAmount":{"description":"Amount in crypto","type":"number"},"transactionHash":{"type":"string"}}},"feeAmount":{"type":"number","description":"Total fee amount in USD"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"resumeUrl":{"type":"string"}}},"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]},"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]}}}}
```

## The Paginated object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}}}}}
```

## The PaginatedOnrampOrders object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"PaginatedOnrampOrders":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"list":{"type":"array","items":{"$ref":"#/components/schemas/OnrampOrder"}}}}]},"Paginated":{"type":"object","properties":{"nextCursor":{"type":"string"}}},"OnrampOrder":{"type":"object","properties":{"_id":{"type":"string","description":"Order ID"},"walletType":{"$ref":"#/components/schemas/OnrampNetwork","description":"Network type"},"asset":{"$ref":"#/components/schemas/OnrampAsset","description":"Asset type"},"walletAddress":{"type":"string","description":"User wallet address"},"feePercent":{"type":"number","description":"total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasUsdAmount":{"type":"number"},"merchantId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"buySwap":{"type":"object","properties":{"_id":{"type":"string"},"buyerUserPhone":{"type":"string"},"buyerUserEmail":{"type":"string"},"sellerUserPhone":{"type":"string"},"amount":{"type":"number","description":"Amount in cents"},"airtimeAmount":{"type":"number"},"status":{"$ref":"#/components/schemas/BuySwapStatus"},"provider":{"$ref":"#/components/schemas/OnRampProvider"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"buyerConfirmedAt":{"type":"string","format":"date-time"},"countryIsoCode":{"type":"string"}}},"withdrawal":{"type":"object","properties":{"_id":{"type":"string"},"status":{"$ref":"#/components/schemas/WithdrawalStatus"},"withdrawAmount":{"description":"Amount in USD","type":"number"},"withdrawCryptoAmount":{"description":"Amount in crypto","type":"number"},"transactionHash":{"type":"string"}}},"feeAmount":{"type":"number","description":"Total fee amount in USD"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"resumeUrl":{"type":"string"}}},"OnrampNetwork":{"type":"string","enum":["POLYGON","ETHEREUM","STELLAR","AVALANCHE","SOLANA","ALGORAND","CELO","BASE","TON","LISK","ARBITRUM","OPTIMISM","BNB"]},"OnrampAsset":{"type":"string","enum":["USDC","USDC_E","USDT","CUSD","CKES"]},"BuySwapStatus":{"type":"string","enum":["initiated","expired","buyer_confirmed","seller_confirmation_pending","seller_confirmation_failed","seller_confirmed","seller_rejected"],"description":"- initiated: The buy swap has been initiated\n- expired: The buy swap has expired\n- buyer_confirmed: The buyer has confirmed the buy swap\n- seller_confirmation_pending: The agent is yet to confirm the buy swap\n- seller_confirmation_failed: The agent has failed to confirm the buy swap\n- seller_confirmed: The agent has confirmed the buy swap\n- seller_rejected: The agent has rejected the buy swap"},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]},"WithdrawalStatus":{"type":"string","enum":["pending","complete","failed"]}}}}
```

## The OnrampPriceResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampPriceResponse":{"type":"object","properties":{"quoteId":{"type":"string","description":"Unique quote id"},"cryptoTotalAmount":{"type":"number","description":"Amount of crypto user should receive before fees"},"cryptoWithdrawAmount":{"type":"number","description":"Amount of crypto user should receive after fees"},"cryptoFeeAmount":{"type":"number","description":"Total fee amount (fonbnk fee + partner fee) in crypto"},"cryptoGasAmount":{"type":"number","description":"Network fee in crypto"},"localCurrencyAmount":{"type":"number","description":"Amount of local currency user should pay"},"feePercent":{"type":"number","description":"Total fee percent (fonbnk fee + partner fee)"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"totalAmount":{"type":"number","description":"Amount of funds user will receive before fees"},"withdrawAmount":{"type":"number","description":"Amount of funds user will receive after fees"},"feeAmount":{"type":"number","description":"Total fee amount (fonbnk fee + partner fee)"},"localCurrencyFeeAmount":{"type":"number","description":"Total fee amount in local currency (fonbnk fee + partner fee)"},"fonbnkFeeAmount":{"type":"number"},"localCurrencyFonbnkFeeAmount":{"type":"number"},"partnerFeeAmount":{"type":"number"},"localCurrencyPartnerFeeAmount":{"type":"number"},"networkFeeAmount":{"type":"number"},"localCurrencyNetworkFeeAmount":{"type":"number"},"usdcTotalAmount":{"type":"number","description":"Amount of usd user will receive before fees (deprecated)"},"usdcWithdrawAmount":{"type":"number","description":"Amount of usd user will receive after fees (deprecated)"},"usdcFeeAmount":{"type":"number","description":"Fonbnk service fee (deprecated)"},"usdcGasAmount":{"type":"number","description":"Network fee (deprecated)"}}}}}}
```

## The OnrampCurrency object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampCurrency":{"type":"string","enum":["usdc","local"]}}}}
```

## The OnrampProviderLimit object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampProviderLimit":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}},"localCurrency":{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"max":{"type":"number"},"min":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}}}}}}}
```

## The OnrampProviderLimitations object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampProviderLimitations":{"type":"object","properties":{"AVALANCHE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"CELO":{"$ref":"#/components/schemas/OnrampProviderLimit"},"POLYGON":{"$ref":"#/components/schemas/OnrampProviderLimit"},"STELLAR":{"$ref":"#/components/schemas/OnrampProviderLimit"},"SOLANA":{"$ref":"#/components/schemas/OnrampProviderLimit"},"BASE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"TON":{"$ref":"#/components/schemas/OnrampProviderLimit"}}},"OnrampProviderLimit":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}},"localCurrency":{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"max":{"type":"number"},"min":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}}}}}}}
```

## The OnrampProvidersListResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampProvidersListResponse":{"type":"array","items":{"type":"object","properties":{"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"providers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"requiresCarrier":{"type":"boolean"},"limits":{"$ref":"#/components/schemas/OnrampProviderLimitations"},"carriers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"limits":{"$ref":"#/components/schemas/OnrampProviderLimitations"}}}}}}}}}},"OnrampProviderLimitations":{"type":"object","properties":{"AVALANCHE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"CELO":{"$ref":"#/components/schemas/OnrampProviderLimit"},"POLYGON":{"$ref":"#/components/schemas/OnrampProviderLimit"},"STELLAR":{"$ref":"#/components/schemas/OnrampProviderLimit"},"SOLANA":{"$ref":"#/components/schemas/OnrampProviderLimit"},"BASE":{"$ref":"#/components/schemas/OnrampProviderLimit"},"TON":{"$ref":"#/components/schemas/OnrampProviderLimit"}}},"OnrampProviderLimit":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}},"localCurrency":{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"max":{"type":"number"},"min":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}}}}}}}
```

## The OnrampLimitsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OnrampLimitsResponse":{"type":"object","properties":{"cryptoLimits":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"localCurrencyLimits":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["open_range","fixed_list"]},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"},"withCents":{"type":"boolean"},"values":{"type":"array","items":{"type":"number"}}}},{"type":"object","properties":{"type":{"type":"string","enum":["fixed_list"]},"values":{"type":"array","items":{"type":"number"}},"withCents":{"type":"boolean"}}}]},"fees":{"type":"object","properties":{"feePercent":{"type":"number"},"fonbnkFeePercent":{"type":"number"},"partnerFeePercent":{"type":"number"},"gasAmount":{"type":"number"},"minFee":{"type":"number"}}}}}}}}
```

## The OfframpOrderStatus object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}}}
```

## The OfframpNetwork object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]}}}}
```

## The OfframpAsset object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]}}}}
```

## The OfframpType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The OfframpPaymentType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"}}}}
```

## The OfframpOrder object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpOrder":{"type":"object","properties":{"_id":{"type":"string"},"offerId":{"type":"string"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"exchangeRate":{"type":"number"},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}},"fromAddress":{"type":"string"},"toAddress":{"type":"string"},"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"hash":{"type":"string"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/OfframpOrderStatus"},"changedAt":{"type":"string","format":"date-time"}}}},"requiredFields":{"type":"object","description":"Fields that was required to be filled by the user"},"countryIsoCode":{"type":"string"},"userPhoneNumber":{"type":"string"},"userEmail":{"type":"string"},"currencyIsoCode":{"type":"string"},"offerRequiredFields":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"type":{"type":"string"},"value":{"type":"string"}}}},"orderParams":{"type":"string"}}},"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpOrderStatus":{"type":"string","enum":["initiated","awaiting_transaction_confirmation","transaction_confirmed","transaction_failed","offramp_success","offramp_pending","offramp_failed","offramp_retry","refunding","refunded","refund_failed","expired","cancelled","validating_transaction","transaction_invalid"],"description":"- initiated: The offramp order has been initiated\n- awaiting_transaction_confirmation: User sent the transaction hash and system is awaiting confirmation\n- transaction_confirmed: User transaction has been confirmed\n- transaction_failed: User transaction has failed\n- offramp_success: The offramp transaction has been successful\n- offramp_pending: The offramp transaction is pending\n- offramp_failed: The offramp transaction has failed\n- offramp_retry: The offramp transaction is being retried\n- refunding: The offramp transaction is being refunded\n- refunded: The offramp transaction has been refunded\n- refund_failed: The offramp transaction refund has failed\n- expired: The offramp order has expired\n- cancelled: The offramp order has been cancelled by the user\n- validating_transaction: The offramp transaction is being validated\n- transaction_invalid: The offramp transaction is invalid"}}}}
```

## The RequiredFieldType object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"RequiredFieldType":{"type":"string","enum":["number","string","date","boolean","email","enum"]}}}}
```

## The BestOfframpOfferResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"BestOfframpOfferResponse":{"type":"object","properties":{"offer":{"type":"object","properties":{"_id":{"type":"string"},"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"exchangeRate":{"type":"number"},"cryptoExchangeRate":{"type":"number"},"requiredFields":{"type":"object","properties":{"fieldName":{"type":"object","properties":{"type":{"ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}}}}}},"type":{"$ref":"#/components/schemas/OfframpType"}}},"cashout":{"type":"object","properties":{"localCurrencyAmount":{"type":"number"},"usdAmount":{"type":"number"},"feeAmountUsd":{"type":"number"},"feeAmountUsdFonbnk":{"type":"number"},"feeAmountUsdPartner":{"type":"number"},"feeAmountLocalCurrency":{"type":"number"},"feeAmountLocalCurrencyFonbnk":{"type":"number"},"feeAmountLocalCurrencyPartner":{"type":"number"},"cryptoAmount":{"type":"number"},"feeAmountCrypto":{"type":"number"},"feeAmountCryptoFonbnk":{"type":"number"},"feeAmountCryptoPartner":{"type":"number"}}}}},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The OfframpAmountCurrency object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpAmountCurrency":{"type":"string","enum":["local","usd"]}}}}
```

## The OfframpLimitsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpLimitsResponse":{"type":"object","properties":{"minUsd":{"type":"number"},"maxUsd":{"type":"number"},"minLocalCurrency":{"type":"number"},"maxLocalCurrency":{"type":"number"}}}}}}
```

## The OfframpSupportedCountry object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpSupportedCountry":{"type":"object","properties":{"countryIsoCode":{"type":"string"},"currencyIsoCode":{"type":"string"},"name":{"type":"string"},"offrampTypes":{"type":"array","items":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OfframpType"},"name":{"type":"string"},"carriers":{"type":"array","items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}}},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]}}}}
```

## The OfframpWallet object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpWallet":{"type":"object","properties":{"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"}}},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]}}}}
```

## The OfframpValidateFieldsRequest object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpValidateFieldsRequest":{"type":"object","properties":{"offerId":{"type":"string","description":"ID of the offer returned from get best offer endpoint"},"requiredFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Object with user required fields"}},"required":["offerId","requiredFields"]}}}}
```

## The OfframpValidateFieldsResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"OfframpValidateFieldsResponse":{"type":"object","properties":{"details":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"}}}}}}}}}
```

## The CreateOrderRequest object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"CreateOrderRequest":{"type":"object","properties":{"offerId":{"type":"string","description":"ID of the offer returned from get best offer endpoint"},"requiredFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Object with user required fields"},"paymentType":{"$ref":"#/components/schemas/OfframpPaymentType"},"network":{"$ref":"#/components/schemas/OfframpNetwork"},"asset":{"$ref":"#/components/schemas/OfframpAsset"},"address":{"type":"string","description":"Address of the wallet from which funds will be sent"},"currency":{"$ref":"#/components/schemas/OfframpAmountCurrency"},"amount":{"type":"number","description":"Amount of usd user wants to pay or amount of local currency user wants to receive depending on the currency param value"},"ip":{"type":"string","description":"IP address of a user"},"orderParams":{"type":"string","description":"OrderParams that need to be associated with an order"}},"required":["offerId","requiredFields","paymentType","network","asset","address","currency","amount"]},"OfframpPaymentType":{"type":"string","enum":["CRYPTO_WALLET","VIRTUAL_WALLET"],"description":"- CRYPTO_WALLET: User will pay with a crypto wallet\n- VIRTUAL_WALLET: Order will be paid from a merchant's virtual wallet"},"OfframpNetwork":{"type":"string","enum":["AVALANCHE","POLYGON","CELO","ETHEREUM"]},"OfframpAsset":{"type":"string","enum":["USDC","USDT","CUSD","CKES"]},"OfframpAmountCurrency":{"type":"string","enum":["local","usd"]}}}}
```

## The ConfirmOrderRequest object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"ConfirmOrderRequest":{"type":"object","properties":{"orderId":{"type":"string","description":"ID of the order returned from create order endpoint"},"hash":{"type":"string","description":"Transaction hash for crypto orders"}},"required":["orderId","hash"]}}}}
```

## The KycStateResponse object

```json
{"openapi":"3.1.0","info":{"title":"Fonbnk On-ramp API","version":"1.0.0"},"components":{"schemas":{"KycStateResponse":{"type":"object","properties":{"kycUrl":{"type":"string","description":"URL to the KYC form"},"offrampKycRules":{"type":"array","items":{"description":"Type of the off-ramp","type":{"$ref":"#/components/schemas/OfframpType"}},"minAmount":{"type":"number","description":"Minimum amount of order in USD for KYC to be required"}},"onrampKycRules":{"type":"array","items":{"description":"Type of the on-ramp","type":{"$ref":"#/components/schemas/OnRampProvider"}},"minAmount":{"type":"number","description":"Minimum amount of order in USD for KYC to be required"}},"passedKyc":{"type":"boolean","description":"Indicates if the user has passed KYC"},"kycStatus":{"type":"enum","description":"Status of the last KYC","enum":["initiated","approved","rejected","invalid"],"kycStatusDescription":{"type":"string","description":"Description of the last KYC status"},"reachedKycLimit":{"type":"boolean","description":"Indicates if the user has reached the KYC limit"},"documentTypes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Title of the document"},"value":{"type":"string","description":"ID of the document"},"requiredFields":{"type":"object","description":"Required fields for the document","properties":{"fieldName":{"type":"object","properties":{"type":{"ref":"#/components/schemas/RequiredFieldType"},"label":{"type":"string"},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}},"format":{"type":"string","description":"Format of the field, should be used as an input placeholder"},"regexp":{"type":"string","description":"Regular expression to validate the field"},"regexpFlags":{"type":"string","description":"Regular expression flags"}}}}}}}}}}},"OfframpType":{"type":"string","enum":["bank","airtime","mobile_money","paybill"]},"OnRampProvider":{"type":"string","enum":["bank_transfer","mobile_money","mpesa","carrier"]}}}}
```


# About

<figure><img src="/files/18kfrVmao2O3qpxaPfRS" alt=""><figcaption></figcaption></figure>

Welcome to the Fonbnk Documentation!

#### Overview <a href="#overview" id="overview"></a>

Fonbnk provides a secure and efficient way to facilitate both on-ramp and off-ramp transactions. It supports integrated and non-integrated methods, making it versatile for various use cases.&#x20;

#### Key Features <a href="#key-features" id="key-features"></a>

* **P2P Platform:** Connects buyers and sellers of fiat (Airtime, Mobile Money, Bank, etc.).
* **Crypto Payments:** Facilitates transactions in crypto.
* **Integration Options:** You can either use our Pay Widget web app or do a full flow via server-to-server communication.
* **Merchant Integration**: Supports the flow where a user pays crypto/fiat and the merchant receives funds on their balance or vice versa.

#### Getting Started <a href="#getting-started" id="getting-started"></a>

To get started, choose the integration type that best suits your needs and follow the detailed guides provided in this documentation. Whether you are setting up a simple donation link or a complex merchant integration, the Fonbnk Pay Widget offers a flexible solution for accepting and converting crypto payments.

Explore the documentation to learn more about the features, configurations, and best practices for using the Fonbnk Pay Widget and Fonbnk Merchant API.


# How it works

### On-ramp

An on-ramp allows a user to exchange their local currency for cryptocurrency. How it works for end users:

1. A customer selects his funds source (Airtime, Mobile Money, Bank, etc.) and the amount of crypto he would like to receive
2. Customer provides his wallet details
3. Customer transfers funds to an agent we found for him and confirms the order
4. An agent confirms the order and the system sends crypto to a customer's wallet

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify the amount of crypto to buy
Widget->>User: Show the best offer
User->>Widget: Specify wallet details
User->>Widget: Verify email
User->>Widget: Create order
Widget->>User: Provide transfer funds instructions
Note over User: Send funds to an agent
User->>Widget: Confirm that funds are sent
Note over Agent: Check if funds are received
Agent->>Widget: Confirm that funds are received
Widget->>User: Send crypto to user wallet" %}

Example of Nigeria bank on-ramp:

{% embed url="<https://gumlet.tv/watch/696784c6b25141dfa4cca3f0/>" %}

### Off-ramp

An off-ramp allows a user to exchange their cryptocurrency for their country's local currency . How it works for end users:

1. Customer selects off-ramp type and specifies the amount of crypto he wants to exchange. System displays how much local currency he will receive.
2. Customer verifies his email by entering a code sent to him.
3. Customer provides his account details such as bank account number, bank name, etc.
4. System returns a wallet address where customer should send his crypto.
5. Customer sends crypto to the provided address and provides a transaction hash to the system.
6. System checks if the transaction is received and sends local currency to the customer's account.

{% @mermaid/diagram content="sequenceDiagram
User->>Widget: Specify amount of crypto to exchange
Widget->>User: Show the best offer
User->>Widget: Verify email
User->>Widget: Provide account details
User->>Widget: Create order
Widget->>User: Wallet address to send crypto
Note over User: Send crypto to the wallet
User->>Widget: Send transaction hash
Note over Widget: Check if funds are received
Widget->>User: Send local currency to the user account
" %}

Example of the Kenya Mobile Money off-ramp:

{% embed url="<https://gumlet.tv/watch/69678576b25141dfa4ccb1ef/>" %}


# Supported countries and cryptocurrencies

### Available countries and payment methods

<table><thead><tr><th>Country name</th><th>countryIsoCode</th><th>currencyIsoCode</th><th>on-ramp payment channels</th><th>off-ramp payment channels</th><th width="305.4140625">Country specifics</th></tr></thead><tbody><tr><td>Nigeria</td><td>NG</td><td>NGN</td><td>bank</td><td>bank, airtime</td><td></td></tr><tr><td>Kenya</td><td>KE</td><td>KES</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Ghana</td><td>GH</td><td>GHS</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>South Africa</td><td>ZA</td><td>ZAR</td><td>bank</td><td>bank, airtime</td><td>In this country, bank transactions can take up to two business days. Consequently, if a user creates an order on Friday, it may be processed by Monday or Tuesday</td></tr><tr><td>Tanzania</td><td>TZ</td><td>TZS</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Uganda</td><td>UG</td><td>UGX</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Zambia</td><td>ZM</td><td>ZMW</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Burkina Faso</td><td>BF</td><td>XOF</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Brazil</td><td>BR</td><td>BRL</td><td>bank</td><td>bank</td><td></td></tr><tr><td>Senegal</td><td>SN</td><td>XOF</td><td>mobile_money</td><td>mobile_money,</td><td></td></tr><tr><td>Republic of the Congo</td><td>CG</td><td>XAF</td><td>mobile_money</td><td>mobile_money</td><td></td></tr><tr><td>Benin</td><td>BJ</td><td>XOF</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Gabon</td><td>GA</td><td>XAF</td><td>mobile_money</td><td>mobile_money</td><td></td></tr><tr><td>Rwanda</td><td>RW</td><td>RWF</td><td>mobile_money</td><td>airtime</td><td></td></tr><tr><td>Ivory Coast</td><td>CI</td><td>XOF</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Cameroon</td><td>CM</td><td>XAF</td><td>mobile_money</td><td>mobile_money, airtime</td><td></td></tr><tr><td>Malawi</td><td>MW</td><td>MWK</td><td>-</td><td>mobile_money</td><td></td></tr><tr><td>Philippines</td><td>PH</td><td>PHP</td><td>bank</td><td>bank</td><td></td></tr></tbody></table>

### Available cryptocurrencies

| Coin              | Available        | Contract                                                 |
| ----------------- | ---------------- | -------------------------------------------------------- |
| CELO\_USDT        | on-ramp/off-ramp | 0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e               |
| ETHEREUM\_NATIVE  | on-ramp          |                                                          |
| SOLANA\_NATIVE    | on-ramp/off-ramp |                                                          |
| LISK\_USDT        | on-ramp/off-ramp | 0x05D032ac25d322df992303dCa074EE7392C117b9               |
| TON\_USDT         | on-ramp/off-ramp | EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id\_sDs        |
| TON\_USDE         | on-ramp          | EQAIb6KmdfdDR7CN1GBqVJuP25iCnLKCvBlJ07Evuu2dzP5f         |
| TRON\_NATIVE      | on-ramp/off-ramp |                                                          |
| BNB\_USDT         | on-ramp/off-ramp | 0x55d398326f99059fF775485246999027B3197955               |
| AVALANCHE\_USDT   | on-ramp/off-ramp | 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7               |
| POLYGON\_USDT     | on-ramp/off-ramp | 0xc2132D05D31c914a87C6611C10748AEb04B58e8F               |
| TRON\_USDT        | on-ramp/off-ramp | TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t                       |
| ETHEREUM\_USDT    | on-ramp/off-ramp | 0xdac17f958d2ee523a2206206994597c13d831ec7               |
| ETHEREUM\_RLUSD   | on-ramp/off-ramp | 0x8292bb45bf1ee4d140127049757c2e0ff06317ed               |
| XRP\_RLUSD        | on-ramp/off-ramp | 524C555344000000000000000000000000000000                 |
| SOLANA\_USDT      | on-ramp/off-ramp | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB             |
| OPTIMISM\_USDT    | on-ramp/off-ramp | 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58               |
| ARBITRUM\_USDT    | on-ramp/off-ramp | 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9               |
| OPTIMISM\_USDC    | on-ramp/off-ramp | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85               |
| AVALANCHE\_USDC   | on-ramp/off-ramp | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E               |
| POLYGON\_USDC     | on-ramp/off-ramp | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359               |
| STELLAR\_USDC     | on-ramp/off-ramp | GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN |
| CELO\_CUSD        | on-ramp/off-ramp | 0x765DE816845861e75A25fCA122bb6898B8B1282a               |
| CELO\_USDC        | on-ramp/off-ramp | 0xcebA9300f2b948710d2653dD7B07f33A8B32118C               |
| ETHEREUM\_USDC    | on-ramp/off-ramp | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48               |
| SOLANA\_USDC      | on-ramp/off-ramp | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v             |
| BASE\_USDC        | on-ramp/off-ramp | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913               |
| ARBITRUM\_USDC    | on-ramp/off-ramp | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831               |
| BNB\_USDC         | on-ramp/off-ramp | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d               |
| CELO\_CKES        | on-ramp/off-ramp | 0x456a3d042c0dbd3db53d5489e98dfb038553b0d0               |
| CELO\_CGHS        | on-ramp/off-ramp | 0xfAeA5F3404bbA20D3cc2f8C4B0A888F55a3c7313               |
| TEMPO\_USDC\_E    | on-ramp          | 0x20c000000000000000000000b9537d11c60e8b50               |
| TEMPO\_PATHUSD    | on-ramp/off-ramp | 0x20c0000000000000000000000000000000000000               |
| BASE\_XDUS        | on-ramp/off-ramp | 0x293d36b129F1E6538A036318499d83BD06eB17E9               |
| ETHEREUM\_XDUS    | on-ramp/off-ramp | 0xa8719F9F4c23266a214112AAf4902c1000c02E7c               |
| POLYGON\_XDUS     | on-ramp/off-ramp | 0xa8719F9F4c23266a214112AAf4902c1000c02E7c               |
| TON\_NATIVE       | on-ramp/off-ramp |                                                          |
| BNB\_NATIVE       | on-ramp/off-ramp |                                                          |
| AVALANCHE\_NATIVE | on-ramp/off-ramp |                                                          |
| CELO\_NATIVE      | on-ramp/off-ramp |                                                          |
| BASE\_NATIVE      | on-ramp/off-ramp |                                                          |
| OPTIMISM\_NATIVE  | on-ramp/off-ramp |                                                          |
| ARBITRUM\_NATIVE  | on-ramp/off-ramp |                                                          |
| POLYGON\_NATIVE   | on-ramp/off-ramp |                                                          |


# Fiat transfer types

When a user creates an on-ramp order, we display transfer instructions for him to pay. These instructions can be one of the following types:

* manual
* STK push
* redirect

## Manual transfers

For manual transfers, the system displays a list of instructions required to send funds, and a user has to send them manually.

<figure><img src="/files/4Ws212xglmeAAQrwhkPt" alt=""><figcaption></figcaption></figure>

An example of Nigeria on-ramp manual transfer:

{% embed url="<https://gumlet.tv/watch/696784c6b25141dfa4cca3f0/>" %}

## STK Push

**STK Push** is a native mobile phone popup initiated by a mobile carrier. It asks a user to confirm a transfer of their mobile money funds by entering their PIN code.

Before initiating an STK push, we ask a user to verify his phone number by entering an OTP code.&#x20;

<figure><img src="/files/JTClYTHtJsDBgIUQJ8MY" alt=""><figcaption></figcaption></figure>

An example of Kenya Mobile Money on-ramp STK Push:<br>

{% embed url="<https://gumlet.tv/watch/696a2088828f3379e5406d13/>" %}

## Redirect

The redirect transfer type requires a user to go to our partner's website to finish the order.

<figure><img src="/files/ksCbjgFmpfFCkOGywpGS" alt=""><figcaption></figcaption></figure>


# Fiat payment channels

Fonbnk currently supports three fiat payment channels: Bank, Mobile Money, and Airtime.

### Bank&#x20;

**Supported Flows**: On-Ramp & Off-Ramp

[**Supported Transfer Types**](/fiat-transfer-types): manual, redirect

For both flows, a user must provide the following details to create an order:

* phone number
* user's bank name
* user's bank account number

For on-ramp, when a user creates an order, the system shows the bank details where a user should send funds to.

An example of the transfer instructions for bank on-ramp Nigeria:

\
![](/files/yKMXrNuw48ZJGOU3akhh)

A user must send funds to a specified bank account and set a provided bank transfer narration. When the system detects a transaction to a specified bank account from the bank account provided by a user with an appropriate transfer narration, the crypto funds are released to a user.

Nigeria bank on-ramp example:

{% embed url="<https://gumlet.tv/watch/696784c6b25141dfa4cca3f0/>" %}

Nigeria bank off-ramp example:

{% embed url="<https://gumlet.tv/watch/696a226a828f3379e540a522/>" %}

### Mobile money

**Supported Flows**: On-Ramp & Off-Ramp

[**Supported Transfer Types**](/fiat-transfer-types): manual, STK push

Mobile Money is digital currency stored in an account linked to a mobile number. It is regulated like a bank account, meaning you can deposit cash, send it, and withdraw it back as physical cash. It differs from airtime, which stores credit purchased specifically to use telecommunication services (calls, SMS, data).

For both flows, a user must provide the following details to create an order:

* user's mobile carrier name
* user's phone number

&#x20;For on-ramp, when a user creates an order, the system asks a user to verify his phone number by providing an OTP code and then sends an STK Push notification to initiate the transfer of mobile money funds.\
When the system detects a transfer from a user, it releases the crypto.\
\* **STK Push** is a native mobile phone popup initiated by a mobile carrier. It asks a user to confirm a transfer of their mobile money funds by entering their PIN code.

An example of the transfer instructions for mobile money on-ramp in Kenya:

\
![](/files/U0Xw3Oy7Bwo4l61abHIo)

Kenya mobile money on-ramp example:

{% embed url="<https://gumlet.tv/watch/696a2327828f3379e540b699/>" %}

Kenya mobile money off-ramp example:

{% embed url="<https://gumlet.tv/watch/69678576b25141dfa4ccb1ef/>" %}

### Airtime

**Supported Flows**: Off-Ramp&#x20;

A user must provide the following details to create an order:

* user's mobile carrier name
* user's phone number

After the user completes the crypto payment, the equivalent value in airtime is automatically topped up to their mobile account.

An example of Nigeria Airtime off-ramp:

{% embed url="<https://gumlet.tv/watch/696a229805ff587e8df6fc6e/>" %}


# KYC

We require our customers to pass the Know Your Customer (KYC) procedure to create an order. The KYC requirement depends on a country, order amount, and a flow (on-ramp or off-ramp).&#x20;

There are two levels of KYC:

* <mark style="color:yellow;">**basic**</mark>
* <mark style="color:yellow;">**advanced**</mark>

For basic KYC, a user needs to select an ID type he wants to submit, provide his first name, last name, date of birth, and an ID number.

For advanced KYC, a user needs to select an ID type they would like to submit, provide their first name, last name, and date of birth, and instead of an ID number, they have to submit 3 photos:

* selfie
* front of the document
* back of the document

KYC requirements for countries:

| Country               | On-ramp                                                                                                                                                          |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nigeria               | <p></p><p>$15 → $500 - <mark style="color:yellow;"><strong>basic</strong></mark></p><p></p>                                                                      |
| Kenya                 | <p>$15 → $99.99 - <mark style="color:yellow;"><strong>basic</strong></mark></p><p>$100 → $500 - <mark style="color:yellow;"><strong>advanced</strong></mark></p> |
| Ghana                 | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| South Africa          | <p>$15 → $99.99 - <mark style="color:yellow;"><strong>basic</strong></mark></p><p>$100 → $500 - <mark style="color:yellow;"><strong>advanced</strong></mark></p> |
| Tanzania              | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Uganda                | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Zambia                | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Burkina Faso          | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Brazil                | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Senegal               | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Republic of the Congo | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Benin                 | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Gabon                 | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Rwanda                | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Ivory Coast           | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |
| Cameroon              | $15 → $500 - <mark style="color:yellow;">**advanced**</mark>                                                                                                     |

Unverified users may transact up to a cumulative $15 (onramp); once exceeded, KYC must be completed before any further transactions.&#x20;

**An example of a basic KYC form**:

<figure><img src="/files/cWnKzCOUHJvNZHz4dRuU" alt=""><figcaption></figcaption></figure>

An example of an advanced KYC form:

<figure><img src="/files/GCmCJxVRHpNlij3EYv2H" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/BNMAQqOclHGKfy2Yoodj" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/HV361oxAo6lLayFoNVhq" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/pRKAmu4lrGFQxrppPeU2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/BtYkUSRRiR2Q2rMX9PBW" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/qKCvZcZMu4hTNCS7kfiB" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/XxB5bMoAEY4w1dpSIpy9" alt=""><figcaption></figcaption></figure>


# Getting started

To use our Pay Widget, you must register in our merchant dashboard using the following links:

{% hint style="info" %}
We recommend to start with the Sandbox environment first
{% endhint %}

| Environment | URL                                          |
| ----------- | -------------------------------------------- |
| Sandbox     | <https://sandbox-dashboard.fonbnk.com/login> |
| Production  | <https://dashboard.fonbnk.com/>              |

After registration, go to the Navigation -> API Settings. There you find 2 values that are required for the integration:

* <mark style="color:yellow;">**Source param**</mark>
* <mark style="color:yellow;">**URL signature secret**</mark>

Then go to the Navigation -> Webhooks -> Settings and set a URL you want to receive webhooks to.

{% hint style="info" %}
You can use the URL from <https://webhook.site/> at the first stage, just to see how it works
{% endhint %}

With the parameters and webhook set up, you're ready to configure the Pay Widget for your needs.

#### Widget URLs: <a href="#off-ramp-url" id="off-ramp-url"></a>

| Sandbox    | ​[https://sandbox-pay.fonbnk.com](https://sandbox-pay.fonbnk.com/offramp)​ |
| ---------- | -------------------------------------------------------------------------- |
| Production | ​[https://pay.fonbnk.com](https://pay.fonbnk.com/offramp)​                 |


# Signing the URL

The Pay Widget is configured by URL query parameters.&#x20;

As the first step, you need to provide 2 params in the URL that will identify your merchant in our system:

1. <mark style="color:yellow;">**source**</mark> - the "source param" value from the merchant dashboard
2. <mark style="color:yellow;">**signature**</mark> - the JWT token generated using the "URL signature secret" from the merchant dashboard

### Generating the signature

You must provide a unique **signature** parameter, which is a JWT token (HS256 encryption algorithm) generated using the "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow creating more than 1 order using the same signature. During testing, you can generate a JWT signature using this website: <https://jwt.io/>. You can also provide URL configuration parameters in the JWT token payload.

An example of signature generation

```typescript
import * as jsonwebtoken from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';

const signature = jsonwebtoken.sign(
    {
      uid: uuid(),
    },
    YOUR_SIGNATURE_SECRET,
    {
      algorithm: 'HS256',
    },
 );
```

After you have the source and the signature params, you can proceed to configure the widget with [URL params](/widget-integration/url-params)


# URL params

Here's a list of URL params that allow you to configure the Pay Widget.

| Parameter       | Flows              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| address         | on-ramp            | <p>Address of the wallet you want to receive crypto to<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| network         | on-ramp / off-ramp | Wallet network. Supported values: ARBITRUM, AVALANCHE, BASE, BNB, CELO, ETHEREUM, LISK, OPTIMISM, POLYGON, SOLANA, STELLAR, TON, TRON                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| asset           | on-ramp / off-ramp | Wallet asset. Supported values: **USDC**, **CUSD**, **USDT**, **CKES, CGHS**, depending on the network. The default value is **USDC** for all networks that support it, except CELO, which is CUSD for CELO. Supported network/asset pairs: AVALANCHE (USDC/USDT), POLYGON (USDC, USDT), CELO (CUSD, USDC, USDT, CKES, CGHS), STELLAR (USDC), SOLANA (USDC, USDT),  BASE (USDC), ETHEREUM (USDC, USDT),  LISK (USDT), OPTIMISM (USDC, USDT), BNB (USDC, USDT), ARBITRUM (USDC, USDT), TRON (USDT), TON (USDT, USDE)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| memo            | on-ramp            | Memo for the Stellar, TON and other networks transactions that support it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| amount          | on-ramp / off-ramp | If a currency is not provided, it will be an amount of crypto received after fees. If currency is **local,** it will be the amount of local currency a user should spend.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| currency        | on-ramp / off-ramp | Currency of the amount. Supported values: **local** or **crypto**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| countryIsoCode  | on-ramp / off-ramp | default selected country iso code, example: **KE** for Kenya, **NG** for Nigeria                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| currencyIsoCode | on-ramp / off-ramp | currency iso code, example: **KES** for Kenya, **NGN** for Nigeria. Acts like a country parameter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| freezeAmount    | on-ramp / off-ramp | Freezes the amount of order for the user, the user will not be able to change it. The amount is required in the URL for this parameter to work.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| freezeWallet    | on-ramp            | <p>Freezes the wallet of order for the user, the user will not be able to change it. The wallet is required in the URL for this parameter to work.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter, also a valid signature parameter should be present. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| paymentChannel  | on-ramp / off-ramp | Default user funds source to select, supported values: **airtime**, **mobile\_money**, **bank**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| carrierCode     | on-ramp / off-ramp | The code of a mobile carrier to select by default. Examples: ng\_mtn, ke\_safaricom etc.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| callbackUrl     | on-ramp            | <p>if present, "Back to website" link will be displayed on the success page. When a user clicks on it, we will redirect him to the provided URL. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p>                                                                                                                                                                                                                                                                                                                        |
| callbackBtnText | on-ramp            | Text of the button that is displayed when **callbackUrl** is provided. Default is: "Back to website"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| email           | on-ramp / off-ramp | user's email                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| closeBtn        | on-ramp            | text of the button that will be displayed on the success page. If not provided, the button will not be displayed. On click, it will send a *close-iframe* iframe event, so an integrator can close the widget.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| redirectUrl     | on-ramp            | <p>if present, user will be redirected to this URL on order fail or success. It supports placeholders which will be replaced by order data: <strong><code>{orderId}</code></strong>, <strong><code>{transactionHash}</code></strong>, <strong><code>{usdcAmount}</code></strong>, <strong><code>{airtimeAmount}</code></strong>, <strong><code>{network}</code></strong>, <strong><code>{address}</code></strong>, <strong><code>{status}</code></strong>, <strong><code>{failReason}</code></strong>. <strong><code>{status}</code></strong> placeholder can the next values: <strong><code>success</code></strong> or <strong><code>fail</code></strong>. Fail reason placeholder can the next values: <strong><code>transaction\_failure</code></strong> or <strong><code>agent\_rejected</code></strong>. For example the next URL <code><https://example.com/success/{orderId}/{usdcAmount}></code> will be converted to something like <code><https://example.com/success/648b3095a9f38d8b7b2da748/5.45></code>.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> provided URL should be encoded, <a href="https://meyerweb.com/eric/tools/dencoder/">example</a></p> |
| quoteId         | on-ramp / off-ramp | id of a quote returned from the [price API request](https://docs.fonbnk.com/docs/pay-widget/merchant-api#get-expected-price).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| hideSwitch      | on-ramp / off-ramp | if present, hides the Buy/Sell switch at the top                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| orderParams     | on-ramp / off-ramp | This parameter will be sent to a merchant webhook after the success of the crypto transfer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| source          | on-ramp / off-ramp | <p>parameter used to match an order to a merchant.<br><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| signature       | on-ramp / off-ramp | <p>A JWT token (HS256 encryption algorithm) is generated using the "URL signature secret" value as a secret. You must add some unique value to the token payload to make each token unique because we don't allow creating more than one order using the same signature.</p><p><mark style="color:orange;"><strong>\[Warning]</strong></mark> In production, you must be a verified merchant to use this parameter. Please contact our team for a KYB process.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |


# Integration examples

This section assumes that you already have a registered merchant and know how to sign the pay widget URLs.

### On-ramp to a predefined address

Let's say you want a user to pay fiat, and the system must send 10 Polygon USDT to this wallet address: 0x41018795fA95783117242244303fd7e26e964eE8. To do so, you must provide the next URL params:

* source
* signature
* network - POLYGON
* asset - USDT
* address - 0x41018795fA95783117242244303fd7e26e964eE8
* amount - 10
* currency - crypto
* freezeWallet - true, doesn't allow a user to change the wallet address
* freezeAmount - true, doesn't allow a user to change the amount

So the end URL would look like next:

{% code overflow="wrap" %}

```
https://sandbox-pay.fonbnk.com?source=...&signature=...&network=POLYGON&asset=USDT&address=0x41018795fA95783117242244303fd7e26e964eE8&amount=10&currency=crypto&freezeWallet=true&freezeAmount=true
```

{% endcode %}

If a user creates an order using this URL, you will receive webhooks to the configured server endpoint in the merchant dashboard.

### Off-ramp to a specific fiat type

Let's say you want a user to buy 50000 NGN using his Celo CUSD wallet. To do so, you must provide the next URL params:

* source
* signature
* network - CELO
* asset - CUSD
* amount - 50000
* currency - local
* countryIsoCode - NG

So the end URL would look like next:

{% code overflow="wrap" %}

```
https://sandbox-pay.fonbnk.com/offramp?source=...&signature=...&network=CELO&asset=CUSD&amount=50000&currency=local&countryIsoCode=NG
```

{% endcode %}

### On-ramp using a quote

Let's say you're an on-ramp aggregator and want to display our pricing before redirecting a user to our widget, and you want a price to remain unchanged. To do so, you must make an API request to our [create quote endpoint](/server-to-server/api-endpoints/create-quote) and use the quoteId from the response as a URL query parameter. Let's say you want a user to convert his M-PESA 1000KES to Tron USDT, you must provide the next URL params:

* source
* signature
* quoteId - quote ID received from the API response
* network - TRON
* asset - USDT
* amount - 1000
* currency - local
* paymentChannel - mobile\_money
* countryIsoCode - NG

So the end URL would look like next:

{% code overflow="wrap" %}

```
https://sandbox-pay.fonbnk.com/offramp?source=...&signature=...&quoteId=...&network=TRON&asset=USDT&amount=1000&currency=local&countryIsoCode=KE&paymentChannel=mobile_money
```

{% endcode %}


# Skipping screens

### Typical flow

A typical user order follows these steps:

1. **Amount Selection**:\
   The user opens the "Amount" page, selects their country and preferred payment method, chooses a cryptocurrency, enters the desired amount, and clicks **"Next"**.
2. **Wallet Connection**:\
   The user is taken to the Wallet page, where they connect their wallet via MetaMask, WalletConnect, or another supported option. Once the wallet is successfully connected, the user is automatically redirected to the next step.
3. **Authentication**:\
   On the Auth page, the user enters their email address and submits a one-time password (OTP) sent to them.
   * If the order amount exceeds a certain threshold, the user is prompted to complete **KYC verification** by submitting ID document details, photos, and a selfie.
4. **Order Details**:\
   The user reviews the order details, fills in any required additional information (e.g., phone number, bank type), and clicks **"Transfer Funds"** to create the order. This redirects them to the next page.
5. **Transfer Instructions**:\
   The user receives instructions on how to transfer funds to the agent handling the order.\
   After sending the funds, the user clicks **"Confirm"**. Once the transaction is approved, the cryptocurrency is delivered to the user's wallet.

Most of the provided pages can be skipped, so a user journey would be much shorter.<br>

### Skipping the Amount page

To skip the Amount page, the next [URL params](/widget-integration/url-params) must be predefined:

* countryIsoCode
* network
* asset
* currency
* amount
* source (from your merchant dashboard)
* signature

{% hint style="warning" %}
In production, you must be a verified merchant to generate signatures and predefine the wallet address
{% endhint %}

A signature is  a JWT token (HS256 encryption algorithm) generated using the "URL signature secret" value as a secret (from the merchant dashboard). You must add some unique value to the token payload to make each token unique because we don't allow creating more than 1 order using the same signature. During testing, you can generate a JWT signature using this website: <https://jwt.io/>. <br>

An example of a token generation in typescript:

```typescript
import * as jsonwebtoken from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';

const token = jsonwebtoken.sign(
    {
      uid: uuid(),
    },
    YOUR_SIGNATURE_SECRET,
    {
      algorithm: 'HS256',
    },
 );
```

So, if you want to create an order for Nigeria via bank transfer for 2 CELO USDT, the URL will be the next:&#x20;

{% code overflow="wrap" %}

```
https://pay.fonbnk.com/wallet?signature=...source=xsdf_2&network=CELO&asset=USDT&amount=2&currency=crypto&paymentChannel=bank&countryIsoCode=NG
```

{% endcode %}

You've skipped the Amount page by opening the wallet page with preconfigured order params.

### Skipping the Wallet page

To skip the wallet page, we need to add 1 more parameter to the existing ones:

* address
* signature

The "address" param contains the user's wallet address.

So, if you want to skip the wallet page, the URL should look like this:<br>

{% code overflow="wrap" %}

```
https://pay.fonbnk.com/auth?signature=...&source=xsdf_2&network=CELO&asset=USDT&amount=2&currency=crypto&paymentChannel=bank&countryIsoCode=NG&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA
```

{% endcode %}

\
You've skipped the Wallet page by opening the Auth page with preconfigured order params and a wallet address.

### Skipping the Auth page

To skip the auth page, you must log in on behalf of a user and provide his access and refresh tokens to the URL.

This step requires you to interact with our Merchant API. [Here](/server-to-server/signing-requests) you can find how to send requests.

You must call[ this endpoint](/server-to-server/api-endpoints/generate-user-auth-tokens) with user email and country ISO code and in the response you'll get the access and refresh tokens.

```javascript
{​
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",​
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"​
​}
```

{% hint style="warning" %}
This type of API calls are disabled for merchants by default. Please contact our support team to enable this feature for your merchant.
{% endhint %}

After getting the tokens, you must add them to the URL as "at"  and "rt" params, so the URL would look like this:

{% code overflow="wrap" %}

```

https://pay.fonbnk.com/swap?signature=...&source=xsdf_2&network=CELO&asset=USDT&amount=2&currency=crypto&paymentChannel=bank&countryIsoCode=NG&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

{% endcode %}

\
You've skipped the Auth page by opening the Order page with preconfigured order params, wallet address with signature, and already logged-in user via access and refresh token URL params.

### Skipping KYC

KYC step can be skipped only by removing the KYC requirement from the merchant, so we don't ask for KYC for this merchant's orders. This can be done by contacting our support team and only if you do KYC on your side already.

### Skipping the Order page

To skip the order page, you must predefine 2 more URL parameters:

* quoteId
* requiredFields

Both of these params you get from the [Create quote endpoint](/server-to-server/api-endpoints/create-quote) . The response will be like this:

```json
{
    "quoteId": "6928130ca263ba8d44fad2cf",
     ...
    "deposit": {
                "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    },
                    {
                        "label": "Deposit underpayment (50%)",
                        "value": "deposit_underpayment"
                    },
                    {
                        "label": "Deposit overpayment (200%)",
                        "value": "deposit_overpayment"
                    }
                ]
            }
        ],
    },
    "payout": {
        ...
        "fieldsToCreateOrder": [
            {
                "key": "blockchainWalletAddress",
                "type": "string",
                "label": "Your wallet address",
                "required": true
            },
            {
                "key": "blockchainMemo",
                "type": "string",
                "label": "Memo",
                "required": false
            },
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "required": false,
                "defaultValue": "payout_success",
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed",
                        "value": "payout_failed"
                    }
                ]
            }
        ]
    }
}
```

Now you must fill a required field object, stringify it and encode for URL safety and provide quoteId and requiredFields to the URL. You must provide all the required fields from deposit and payout.\
\
Required fields encoding example:

```typescript
const values = {
  bankAccountNumber: "234525252525",
  //rest of the fields
};
const encoded = encodeURIComponent(JSON.stringify(values));
// %7B%22phoneNumber%22%3A%222346034088631%22%7D
```

Now, you must add all the parameters to the /auto-order page, aslo the flow=onramp must be added because this page can be used for off-ramps too:

\
<https://pay.fonbnk.com/auto-order?source=xsdf_2&network=CELO&asset=USDT&amount=2&currency=crypto&paymentChannel=bank&countryIsoCode=NG&address=0x91b0a33dbcb10f8331eD3627B94e5a9B1591269f&signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ3ZmVuZmVrbndmZWtud2Zua2plMzIyMjEzMTIzMTIzMTIzIn0.bkFNaPYEeLNoUv7RhCWWROdbsGgJCQQp9Xpk628EoJA&at=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&rt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&quoteId=6878df150d6289ffdedcd6f4&requiredFields=%7B%bankAccountNumber%22%3A%222346034088631%22%7D&flow=onramp>\
\
When a user lands on this page, an order will be created automatically, and a user will see the transfer instructions page.<br>

### Recommended way

You can use /auto-order page for all the cases above; just provide as much info as you can to this page and it'll automatically redirect to the appropriate page, don't forget to add the flow=onramp param there.


# Native wallet integration

We want to ensure a seamless payment and connection experience for users, regardless of the platform they are coming from.

Our website fully supports standard [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum provider injection. This means if your mobile application (via WebView) or browser extension injects a standard provider into `window.ethereum`, your users can immediately interact with our dApp without any additional configuration.

#### Partner UI Customization

While standard injection works out-of-the-box, we offer an enhanced integration tier for our partners.

We understand the value of brand consistency. When a user accesses our site specifically through your application's WebView, a generic "Connect Wallet" button may not feel like a native part of the experience.

To solve this, we support UI injection customization. By adding a specific, pre-agreed property (flag) to your injected `window.ethereum` object, our frontend can detect your specific environment.

When detected, we can replace standard buttons like "Connect Wallet" with a customized button tailored to your brand—using your name, logo, and color palette.

**Example**

Instead of a generic connect button, a user opening our site inside the "Trust Wallet" app could see a branded experience like the one below:

<figure><img src="/files/XSxymgYZdQ1MgzHOc3q2" alt=""><figcaption></figcaption></figure>

#### How to Get Started

If you are a wallet provider or have an app with an embedded crypto browser and want to provide this native feel for your users:

1. Contact Us: Reach out to our partnerships team at <hello@fonbnk.com>.
2. Define the Flag: We will agree on a unique string identifier to look for on the `window.ethereum` object (e.g., `window.ethereum.isAlphaWallet = true`).
3. Provide Assets: Send us your preferred button label text, hex color codes, and SVG logo icon.

We will handle the implementation on our end to ensure your users feel right at home when transacting on our platform.


# Getting Started

{% hint style="info" %}
You also can test our API using [our Postman collection](https://documenter.getpostman.com/view/27894063/2sB3dMwVxZ#155ccb69-7bd2-45f7-a3d5-3b29f4263fbe)
{% endhint %}

To use our API, you must register in our merchant dashboard using the following links:

{% hint style="info" %}
We recommend to start with the Sandbox environment first
{% endhint %}

| Environment | URL                                          |
| ----------- | -------------------------------------------- |
| Sandbox     | <https://sandbox-dashboard.fonbnk.com/login> |
| Production  | <https://dashboard.fonbnk.com/>              |

After registration, go to the Navigation -> API Settings. There you find 2 values that are required for the integration:

* <mark style="color:yellow;">**API signature secret**</mark>
* <mark style="color:yellow;">**Client ID**</mark>

Then go to the Navigation -> Webhooks -> Settings and set a URL you want to receive webhooks to.

{% hint style="info" %}
You can use the URL from <https://webhook.site/> at the first stage, just to see how it works
{% endhint %}

With the API parameters and webhook set up, you're ready to make requests to our API. Each request must be correctly signed using the parameters provided. For instructions, refer to the [Signing Requests](/server-to-server/signing-requests) page.


# Integration guide

Here's a typical flow of the order using our API:

1. Call [Get available currencies](/server-to-server/api-endpoints/get-available-currencies) to list supported currencies, channels, and pairs.
2. Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) for the selected deposit/payout configuration.
3. Call [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state) to determine if KYC is required for the intended amounts and currency types.
   * If KYC is required, call [Submit user KYC](https://github.com/fonbnk/docs/blob/main/api-v2.md#submit-user-kyc) and wait until the status is approved by polling the [get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state) endpoint.
   * See KYC requirements for a detailed checklist of the decision flow.
4. Call [Create a quote](/server-to-server/api-endpoints/create-quote) with the deposit/payout configuration.
   * Use deposit.fieldsToCreateOrder and payout.fieldsToCreateOrder to collect all required fields from the user.
5. Call [Create an order](/server-to-server/api-endpoints/create-order) with quoteId and the collected fields.
6. The user completes the deposit per the transfer instructions on the order.
7. If the transfer requires an intermediate action (stk\_push / otp\_stk\_push), call [Trigger intermediate action](https://github.com/fonbnk/docs/blob/main/api-v2.md#trigger-intermediate-action).
8. Call [Confirm order](/server-to-server/api-endpoints/confirm-order).
9. Use [Get order](/server-to-server/api-endpoints/get-order) to poll for status and details at any time.

{% @mermaid/diagram content="sequenceDiagram
autonumber
actor User as User
participant Merchant as Merchant System
participant Fonbnk as Fonbnk API
participant Agent as Fonbnk Agent<br/>(Liquidity Provider)

```
Note over User, Merchant: Phase 1: Initialization & Discovery

User->>Merchant: Opens "Buy Crypto" Interface

Merchant->>Fonbnk: Call "Get available currencies"
Fonbnk-->>Merchant: Returns supported Pairs<br/>(e.g. NGN, KES, POLYGON_USDT)

Merchant-->>User: Populates Dropdowns

User->>Merchant: Selects Deposit: NGN (Bank) -> Payout: POLYGON_USDT

Merchant->>Fonbnk: Call "Get order limits"
Fonbnk-->>Merchant: Returns Min/Max Limits for NGN -> POLYGON_USDT

Note over User, Merchant: Phase 2: Input & Validation

User->>Merchant: Enters Amount (e.g. 50,000 NGN)

Merchant->>Fonbnk: Call "Get user KYC state"
Fonbnk-->>Merchant: Returns User's Current Tier

Note right of Merchant: Checks if Amount > Tier Limit

opt Upgrade Required
    Merchant-->>User: Prompt for ID Documents
    User->>Merchant: Uploads ID
    Merchant->>Fonbnk: Call "Submit user KYC"
    loop Verification
        Merchant->>Fonbnk: Call "Get user KYC state"
        Fonbnk-->>Merchant: Status: APPROVED
    end
end

Note over User, Merchant: Phase 3: Quote & Data Collection

Merchant->>Fonbnk: Call "Create a quote"
Note right of Merchant: Request: NGN -> POLYGON_USDT
Fonbnk-->>Merchant: Returns Quote & **fieldsToCreateOrder**
Note left of Fonbnk: Requires: "blockchainWalletAddress"

Merchant-->>User: Displays Price + Requests Wallet Address
User->>Merchant: Confirms Price & Enters Wallet Address

Note over User, Agent: Phase 4: Order & Payment

Merchant->>Fonbnk: Call "Create an order"
Fonbnk-->>Merchant: Returns Order (Status: deposit_awaiting)
Note left of Fonbnk: Includes **transferInstructions**<br/>(Agent Bank Details & Ref Code)

Merchant-->>User: Displays Payment Instructions
Note right of User: "Send 50,000 NGN to Agent.<br/>Use Ref Code: ORDER-123"

User->>Agent: Transfers NGN (Manual Bank Transfer)
Note right of User: **MUST** include Ref Code

Merchant->>Fonbnk: Call "Confirm order"
Fonbnk-->>Merchant: Returns updated Order

Fonbnk->>Agent: Verify incoming transaction
Agent-->>Fonbnk: Confirms Funds Received

Note over Merchant, Fonbnk: Phase 5: Completion

Fonbnk-)Merchant: POST Webhook (order-status-change)
Note right of Fonbnk: Status: payout_successful

Merchant->>User: Notify "USDT Sent!"" %}
```


# Transfer types explanation

In our system, after a user creates an order, he must pay funds to the specified recipient. We support several types of fund transfer types:

* <mark style="color:yellow;">**manual**</mark> – user manually makes a transfer with provided instructions
* <mark style="color:yellow;">**redirect**</mark> – user is redirected to a third-party payment page to complete payment
* <mark style="color:yellow;">**stk\_push**</mark> – user receives a push on their phone to approve the payment. If user didn't receive the push notification, you can [Trigger order intermediate action](/server-to-server/api-endpoints/trigger-order-intermediate-action)  to retry it.
* <mark style="color:yellow;">**otp\_stk\_push**</mark> – same as <mark style="color:yellow;">**stk\_push**</mark> but before initiating a push user must verify their phone number by providing the otp code received via SMS or WhatsApp to the [Trigger order intermediate action](/server-to-server/api-endpoints/trigger-order-intermediate-action) endpoint. If user didn't receive the push notification, you can [Trigger order intermediate action](/server-to-server/api-endpoints/trigger-order-intermediate-action)  again to retry it.

Different transfer types contain different data:

* <mark style="color:yellow;">**manual**</mark> – includes transferDetails for making the transfer
* <mark style="color:yellow;">**redirect**</mark> – includes paymentUrl to redirect the user
* <mark style="color:yellow;">**stk\_push**</mark> – includes intermediate action metadata:
  * intermediateActionMaxAttempts
  * intermediateActionAttempts
  * intermediateActionNextAttemptAvailableAt
  * intermediateActionTimeoutMs
* <mark style="color:yellow;">**otp\_stk\_push**</mark> – same as stk\_push plus fieldsForIntermediateAction (e.g., otpCode)

### Example of the manual transfer

Let's imagine you've created a fiat (bank) -> crypto order using the [create order](/server-to-server/api-endpoints/create-order) endpoint.

The <mark style="color:yellow;">order.deposit.transferInstructions</mark> could be like the next:

{% code overflow="wrap" %}

```json
{
  "type": "manual",
  "instructionsText": "Transfer the NGN to the agent's bank account.",
  "warningText": "Important: Only transfer funds from a bank account you specified previously. Send the exact NGN amount. Use the displayed account for this transaction only.",
  "transferDetails": [
    {
      "id": "recipientBankName",
      "label": "Bank name",
      "value": "PROVIDUS BANK"
    },
    {
      "id": "recipientBankAccountNumber",
      "label": "Bank account number",
      "value": "9670555843"
    },
    {
      "id": "recipientBankAccountName",
      "label": "Bank account name",
      "value": "Start Button Limited(Checkout)"
    },
    {
      "id": "bankTransferNarration",
      "label": "Bank transfer narration",
      "description": "TRANSFER WITHOUT NARRATION WILL BE IGNORED BY THE SYSTEM.",
      "value": "shc-pshr4upg8s"
    },
    {
      "id": "amountToSend",
      "label": "Amount to send",
      "value": "14800"
    }
  ],
  "fieldsToConfirmOrder": []
}
```

{% endcode %}

The above data says the following:

* The transfer type is <mark style="color:yellow;">manual</mark>, so the user must initiate and complete the fund transfer independently.
* The user must pay ”<mark style="color:yellow;">14800</mark>” of his local currency to the “9670555843” bank account number and set “<mark style="color:yellow;">shc-pshr4upg8s</mark>” as a transfer narration.

### Example of the stk\_push transfer

Let's imagine you've created an fiat (mobile money) -> crypto order using the [create order](/server-to-server/api-endpoints/create-order) endpoint.

The <mark style="color:yellow;">order.deposit.transferInstructions</mark> could be like the next:

```json
{
  "type": "stk_push",
  "instructionsText": "You’ll be prompted with a USSD dialog to proceed the transfer. If the transfer is unsuccessful or you don’t receive the USSD dialog, please retry the transfer",
  "warningText": "",
  "intermediateActionButtonText": "Retry USSD prompt initialization",
  "intermediateActionMaxAttempts": 3,
  "intermediateActionAttempts": 1,
  "intermediateActionNextAttemptAvailableAt": "2025-12-01T11:54:49.260Z",
  "intermediateActionTimeoutMs": 60000,
  "isIntermediateActionAvailable": true,
  "transferDetails": [
    {
      "id": "amountToSend",
      "label": "Amount to send",
      "value": "635"
    }
  ],
  "fieldsToConfirmOrder": []
}
```

The above data says the following:

* The transfer type is <mark style="color:yellow;">stk\_push</mark>, so the user should receive the push notification to send their funds.
* If the user didn't receive the push notification, you can initiate a new one after "<mark style="color:yellow;">2025-12-01T11:54:49.260Z</mark>". There are 3 attempts to initiate intermediate action, and you can do 2 more. You must call [Trigger order intermediate action](/server-to-server/api-endpoints/trigger-order-intermediate-action)  to retry it.

### Example of the otp\_stk\_push transfer

Let's imagine you've created an fiat (mobile money) -> crypto order using the [create order](/server-to-server/api-endpoints/create-order) endpoint.

The <mark style="color:yellow;">order.deposit.transferInstructions</mark> could be like the next:

{% code overflow="wrap" %}

```json
{
  "type": "otp_stk_push",
  "instructionsText": "Enter the OTP code received via WhatsApp to initiate the transaction and you’ll be prompted with a USSD dialog to proceed the transfer. If the transfer is unsuccessful or you don’t receive the USSD dialog, please retry the transfer",
  "warningText": "",
  "intermediateActionButtonText": "Verify OTP code",
  "intermediateActionMaxAttempts": 3,
  "intermediateActionAttempts": 1,
  "intermediateActionNextAttemptAvailableAt": "1970-01-01T00:00:00.000Z",
  "intermediateActionTimeoutMs": 30000,
  "isIntermediateActionAvailable": true,
  "transferDetails": [
    {
      "id": "amountToSend",
      "label": "Amount to send",
      "value": "310000"
    }
  ],
  "fieldsToConfirmOrder": [],
  "fieldsForIntermediateAction": [
    {
      "key": "otpCode",
      "label": "OTP code",
      "type": "number",
      "required": true
    }
  ],
  "intermediateActionRequired": true,
  "intermediateActionExecuted": false,
  "otpChannel": "whatsapp"
}
```

{% endcode %}

The above data says the following:

* The transfer type is <mark style="color:yellow;">otp\_stk\_push</mark>, so the user should confirm their phone number via OTP and thereafter, they'll receive the push notification to pay for the order.
* System sent the OTP code to user using "<mark style="color:yellow;">whatsapp</mark>" and the [Trigger order intermediate action](/server-to-server/api-endpoints/trigger-order-intermediate-action) must be called with "<mark style="color:yellow;">otpCode</mark>" field to initiate the transfer.


# Flow examples


# Fiat to Crypto

Let’s do an NGN (fiat) deposit to POLYGON\_USDT (crypto) payout.&#x20;

{% stepper %}
{% step %}

#### **First, call** [**Get available currencies and assume you receive:**](/server-to-server/api-endpoints/get-available-currencies)

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "paymentChannels": [
      {
        "type": "bank",
        "transferTypes": [
          "manual",
          "redirect"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      },
      {
        "type": "airtime",
        "transferTypes": [
          "ussd"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel"
          },
          {
            "code": "GLO",
            "name": "Glo"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": false
      },
      {
        "type": "mobile_money",
        "transferTypes": [
          "stk_push",
          "otp_stk_push"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN Mobile Money"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel Money"
          },
          {
            "code": "GLO",
            "name": "Glo Mobile Money"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile Money"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "countryIsoCode": "NG",
    },
    "pairs": [
      "crypto",
      "merchant_balance"
    ]
  },
  {
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT",
    "paymentChannels": [
      {
        "type": "crypto",
        "transferTypes": [
          "manual"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "network": "POLYGON",
      "asset": "USDT",
      "contractAddress": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
    },
    "pairs": [
      "fiat",
      "merchant_balance"
    ]
  }
]
```

{% endcode %}

We see NGN supports deposit via bank/airtime/mobile money, and payout via bank/mobile money. POLYGON\_USDT supports both deposit and payout. So we can do Fiat → Crypto (NGN → POLYGON\_USDT).
{% endstep %}

{% step %}

#### Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) with the next query params:

* depositPaymentChannel: "<mark style="color:yellow;">bank</mark>"
* depositCurrencyType: "<mark style="color:yellow;">fiat</mark>"
* depositCurrencyCode: "<mark style="color:yellow;">NGN</mark>"
* depositCountryIsoCode: "<mark style="color:yellow;">NG</mark>"
* payoutPaymentChannel: "<mark style="color:yellow;">crypto</mark>"
* payoutCurrencyType: "<mark style="color:yellow;">crypto</mark>"
* payoutCurrencyCode: "<mark style="color:yellow;">POLYGON\_USDT</mark>"

{% code title="Example response" overflow="wrap" %}

```json
{
  "deposit": {
    "min": 1556,
    "max": 311184,
    "minUsd": 1,
    "maxUsd": 200
  },
  "payout": {
    "min": 1,
    "max": 200,
    "minUsd": 1,
    "maxUsd": 200
  }
}
```

{% endcode %}

Assume the user wants to receive 100 POLYGON\_USDT. Validate their tier using the [KYC requirements](/server-to-server/kyc-flow) flow.&#x20;
{% endstep %}

{% step %}

#### Call [Create quote:](/server-to-server/api-endpoints/create-quote)

{% code title="Example request" %}

```json
{
  "deposit": {
    "paymentChannel": "bank",
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "countryIsoCode": "NG"
  },
  "payout": {
    "paymentChannel": "crypto",
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT",
    "amount": 100
  }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
  "quoteId": "68628fa56ff494df5f39faf5",
  "quoteExpiresAt": "2024-10-10T10:10:10.000Z",
  "deposit": {
    "paymentChannel": "bank",
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "currencyDetails": {
      "countryIsoCode": "NG",
      "countryName": "Nigeria",
      "countryCode": "234",
      "currencySymbol": "₦",
      "currencyIcon": "https://cdn.example.com/flags/ng.png"
    },
    "cashout": {
      "exchangeRate": 1500,
      "exchangeRateAfterFees": 1531.1269,
      "amountBeforeFees": 153128,
      "amountAfterFees": 150015,
      "amountBeforeFeesUsd": 102.085333,
      "amountAfterFeesUsd": 100.01,
      "chargedFees": [
        {
          "id": "provider_fee",
          "type": "flat_amount",
          "recipient": "provider",
          "amount": 50
        },
        {
          "id": "platform_fee",
          "type": "percentage",
          "recipient": "platform",
          "amount": 3063
        }
      ],
      "chargedFeesUsd": [
        {
          "id": "provider_fee",
          "type": "flat_amount",
          "recipient": "provider",
          "amount": 0.033333
        },
        {
          "id": "platform_fee",
          "type": "percentage",
          "recipient": "platform",
          "amount": 2.042
        }
      ],
      "totalChargedFees": 3113,
      "totalChargedFeesUsd": 2.075333,
      "chargedFeesPerRecipient": {
        "provider": 50,
        "platform": 3063
      },
      "chargedFeesPerRecipientUsd": {
        "provider": 0.033333,
        "platform": 2.042
      },
      "feeSettings": [
        {
          "id": "provider_fee",
          "recipient": "provider",
          "type": "flat_amount",
          "value": 50,
          "min": 0,
          "max": "Infinity"
        },
        {
          "id": "platform_fee",
          "recipient": "platform",
          "type": "percentage",
          "value": 2,
          "min": 0,
          "max": "Infinity"
        }
      ]
    },
    "fieldsToCreateOrder": [
      {
        "key": "phoneNumber",
        "label": "Phone Number",
        "required": true,
        "type": "phone"
      },
      {
        "key": "bankCode",
        "label": "Bank name",
        "required": true,
        "type": "enum",
        "options": [
          {
            "value": "120001:02",
            "label": "9Payment Service Bank"
          },
          {
            "value": "801:02",
            "label": "Abbey Mortgage Bank"
          }
        ]
      },
      {
        "key": "bankAccountNumber",
        "label": "Bank Account Number",
        "required": true,
        "type": "string"
      }
    ],
    "transferType": "manual"
  },
  "payout": {
    "paymentChannel": "crypto",
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT",
    "currencyDetails": {
      "network": "POLYGON",
      "asset": "USDT",
      "contractAddress": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
    },
    "cashout": {
      "exchangeRate": 1,
      "exchangeRateAfterFees": 1.001,
      "amountBeforeFees": 100.01,
      "amountAfterFees": 100,
      "amountBeforeFeesUsd": 100.01,
      "amountAfterFeesUsd": 100,
      "feeSettings": [
        {
          "id": "gas_fee",
          "recipient": "blockchain",
          "type": "flat_amount",
          "value": 0.01,
          "min": 0,
          "max": "Infinity"
        }
      ],
      "chargedFees": [
        {
          "id": "gas_fee",
          "type": "flat_amount",
          "recipient": "blockchain",
          "amount": 0.01
        }
      ],
      "chargedFeesUsd": [
        {
          "id": "gas_fee",
          "type": "flat_amount",
          "recipient": "blockchain",
          "amount": 0.01
        }
      ],
      "totalChargedFees": 0.01,
      "totalChargedFeesUsd": 0.01,
      "chargedFeesPerRecipient": {
        "blockchain": 0.01
      },
      "chargedFeesPerRecipientUsd": {
        "blockchain": 0.01
      }
    },
    "fieldsToCreateOrder": [
      {
        "key": "blockchainWalletAddress",
        "label": "Your wallet address",
        "required": true,
        "type": "string"
      }
    ]
  }
}
```

{% endcode %}

To receive 100 POLYGON\_USDT, user must deposit 153128 NGN. Collect these fields:

* phoneNumber
* bankCode (from enum options)
* bankAccountNumber
* blockchainWalletAddress
  {% endstep %}

{% step %}

#### Create the order via [Create order:](/server-to-server/api-endpoints/create-order)

{% code title="Example request" overflow="wrap" expandable="true" %}

```json
{
  "quoteId": "68628fa56ff494df5f39faf5",
  "userEmail": "someuser@example.com",
  "userIp": "174.3.2.22",
  "deposit": {
    "paymentChannel": "bank",
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "countryIsoCode": "NG"
  },
  "payout": {
    "paymentChannel": "crypto",
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT",
    "amount": 100
  },
  "fieldsToCreateOrder": {
    "phoneNumber": "2348012345678",
    "bankCode": "120001:02",
    "bankAccountNumber": "1234567890",
    "blockchainWalletAddress": "0x5b7ae3c6c83F4A3F94b35c77233b13191eBGAD21"
  }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
const response = {
  "order": {
    "_id": "68728fa56ff494df5f39faf5",
    "countryIsoCode": "NG",
    "userId": "57a28fa56ff494df5f39faf5",
    "userEmail": "someuser@example.com",
    "status": "deposit_awaiting",
    "deposit": {
      //...
      transferInstructions: {
        type: "manual",
        instructionsText: "Please use the following bank details to make a transfer...",
        warningText: "Make sure to include the reference code in your transfer.",
        transferDetails: [
          {id: "recipientBankName", label: "Bank Name", value: "9Payment Service Bank"},
          {id: "recipientBankAccountNumber", label: "Account Number", value: "1234567890"},
          {id: "recipientBankAccountName", label: "Account Name", value: "Example Company Ltd"},
          {id: "amountToSend", label: "Amount to Send", value: "153128"},
          {
            id: "bankTransferNarration",
            label: "Transfer Narration / Reference",
            value: "ORDER-5F8D0D55B54764421B7156C5",
            description: "Use this as the transfer reference."
          }
        ],
        fieldsToConfirmOrder: [],
      }
    },
    "payout": {
      //...
    }
  }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### A user makes the transfer with the exact amount and reference.

{% endstep %}

{% step %}

#### Call [Confirm order if no extra fields are required:](/server-to-server/api-endpoints/confirm-order)

{% code title="Example request" %}

```json
{
  "orderId": "68728fa56ff494df5f39faf5"
}
```

{% endcode %}
{% endstep %}

{% step %}

#### The system validates the deposit and processes payout.&#x20;

{% endstep %}

{% step %}

#### Use [Get order](/server-to-server/api-endpoints/get-order) to track status until "<mark style="color:yellow;">payout\_successful</mark>".

{% endstep %}
{% endstepper %}


# Crypto to Fiat

Let’s do a POLYGON\_USDT (crypto) deposit to NGN (fiat)  payout.&#x20;

{% stepper %}
{% step %}

### First, call [Get available currencies and assume you receive:](/server-to-server/api-endpoints/get-available-currencies)

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "paymentChannels": [
      {
        "type": "bank",
        "transferTypes": [
          "manual",
          "redirect"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      },
      {
        "type": "airtime",
        "transferTypes": [
          "ussd"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel"
          },
          {
            "code": "GLO",
            "name": "Glo"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": false
      },
      {
        "type": "mobile_money",
        "transferTypes": [
          "stk_push",
          "otp_stk_push"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN Mobile Money"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel Money"
          },
          {
            "code": "GLO",
            "name": "Glo Mobile Money"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile Money"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "countryIsoCode": "NG",
    },
    "pairs": [
      "crypto",
      "merchant_balance"
    ]
  },
  {
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT",
    "paymentChannels": [
      {
        "type": "crypto",
        "transferTypes": [
          "manual"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "network": "POLYGON",
      "asset": "USDT",
      "contractAddress": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
    },
    "pairs": [
      "fiat",
      "merchant_balance"
    ]
  }
]
```

{% endcode %}

We see NGN supports deposit via bank/airtime/mobile money, and payout via bank/mobile money. POLYGON\_USDT supports both deposit and payout. So we can do Crypto →  Fiat ( POLYGON\_USDT → NGN).
{% endstep %}

{% step %}

### Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) with the next query params:

* depositPaymentChannel: "<mark style="color:yellow;">crypto</mark>"
* depositCurrencyType: "<mark style="color:yellow;">crypto</mark>"
* depositCurrencyCode: "<mark style="color:yellow;">POLYGON\_USDT</mark>"
* payoutPaymentChannel: "<mark style="color:yellow;">bank</mark>"
* payoutCurrencyType: "<mark style="color:yellow;">fiat</mark>"
* payoutCurrencyCode: "<mark style="color:yellow;">NGN</mark>"
* payoutCountryIsoCode: "<mark style="color:yellow;">NG</mark>"

{% code title="Example response" overflow="wrap" %}

```json
{
    "deposit": {
        "min": 1,
        "max": 500,
        "minUsd": 1,
        "maxUsd": 500
    },
    "payout": {
        "min": 1409,
        "max": 704460,
        "minUsd": 1,
        "maxUsd": 500
    }
}
```

{% endcode %}

We see that a user can send from 1 to 500 POLYGON\_UST and receive from 1409 to 704460 NGN.

Assume the user wants to receive 30 000 NGN. Validate their tier using the [KYC requirements](/server-to-server/kyc-flow) flow.&#x20;
{% endstep %}

{% step %}

### Call [Create quote:](/server-to-server/api-endpoints/create-quote)

{% code title="Example request" %}

```json
{
    "deposit": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT"
    },
    "payout": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG",
        "amount": 30000
    }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692d9874a60e2135463730cf",
    "quoteExpiresAt": "2025-12-01T14:00:28.208Z",
    "deposit": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "cashout": {
            "amountBeforeFees": 21.272315,
            "amountAfterFees": 21.272315,
            "amountBeforeFeesUsd": 21.272315,
            "amountAfterFeesUsd": 21.272315,
            "chargedFees": [],
            "chargedFeesUsd": [],
            "totalChargedFees": 0,
            "totalChargedFeesUsd": 0,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1,
            "chargedFeesPerRecipient": {},
            "chargedFeesPerRecipientUsd": {},
            "feeSettings": []
        },
        "fieldsToCreateOrder": [
            {
                "key": "blockchainWalletAddress",
                "type": "string",
                "label": "Your wallet address",
                "required": true
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "cashout": {
            "amountBeforeFees": 31088,
            "amountAfterFees": 30000,
            "amountBeforeFeesUsd": 21.272315,
            "amountAfterFeesUsd": 20.527839,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 777.2
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 310.88
                }
            ],
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 0.531808
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 0.212723
                }
            ],
            "totalChargedFees": 1088.08,
            "totalChargedFeesUsd": 0.744531,
            "exchangeRate": 1461.43,
            "exchangeRateAfterFees": 1514.4312,
            "chargedFeesPerRecipient": {
                "platform": 777.2,
                "merchant": 310.88
            },
            "chargedFeesPerRecipientUsd": {
                "platform": 0.531808,
                "merchant": 0.212723
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "required": false,
                "defaultValue": "payout_success",
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed",
                        "value": "payout_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}

To receive 30 000 NGN, a user must deposit 21.394438 POLYGON\_USDT. Collect these fields:

* phoneNumber
* bankCode (from enum options)
* bankAccountNumber
* blockchainWalletAddress
  {% endstep %}

{% step %}

### Create the order via [Create order:](/server-to-server/api-endpoints/create-order)

{% code title="Example request" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692d9874a60e2135463730cf",
    "userCountryIsoCode": "NG",
    "userEmail": "testuser+ng@fonbnk.com",
    "userIp": "223.134.123.12",
    "deposit": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT"
    },
    "payout": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG",
        "amount": 30000
    },
    "fieldsToCreateOrder": {
        "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
        "phoneNumber": "2348012345678",
        "bankCode": "1",
        "bankAccountNumber": "1234567890"
    }
}
```

{% endcode %}

{% code title="Example response (transfer instructions excerpt)" overflow="wrap" expandable="true" %}

```json
{
    "quoteUsed": true,
    "order": {
        "_id": "692d98b0a60e21354637311a",
        "countryIsoCode": "NG",
        "userId": "686671f07730d8d1a9b2260a",
        "userEmail": "testuser+ng@fonbnk.com",
        "status": "deposit_awaiting",
        "deposit": {
            "paymentChannel": "crypto",
            "currencyType": "crypto",
            "currencyCode": "POLYGON_USDT",
            "currencyDetails": {
                "network": "POLYGON",
                "asset": "USDT",
                "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
            },
            "cashout": {
                "amountBeforeFees": 21.272315,
                "amountAfterFees": 21.272315,
                "amountBeforeFeesUsd": 21.272315,
                "amountAfterFeesUsd": 21.272315,
                "chargedFees": [],
                "chargedFeesUsd": [],
                "totalChargedFees": 0,
                "totalChargedFeesUsd": 0,
                "exchangeRate": 1,
                "exchangeRateAfterFees": 1,
                "chargedFeesPerRecipient": {},
                "chargedFeesPerRecipientUsd": {},
                "feeSettings": []
            },
            "providedFieldsToCreateOrder": {
                "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
            },
            "transferInstructions": {
                "type": "manual",
                "instructionsText": "Please send the exact amount of crypto to the address below. Make sure to send only USDT on the Polygon network. Sending any other assets or using a different network may result in loss of funds.",
                "transferDetails": [
                    {
                        "id": "recipientWalletAddress",
                        "label": "Wallet address to send",
                        "value": "0xdc9cbad0c43f912a66cd44cd22a15c04368e659f"
                    },
                    {
                        "id": "senderWalletAddress",
                        "label": "Your wallet address",
                        "value": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
                    },
                    {
                        "id": "amountToSend",
                        "label": "Amount to send",
                        "value": "21.272315"
                    },
                    {
                        "id": "cryptoTransactionRequestAdditionalData",
                        "label": "Crypto transaction additional data",
                        "value": ""
                    }
                ],
                "fieldsToConfirmOrder": [
                    {
                        "key": "blockchainTransactionHash",
                        "type": "string",
                        "label": "Transaction hash",
                        "required": true
                    }
                ]
            }
        },
        "payout": {
            "paymentChannel": "bank",
            "currencyType": "fiat",
            "currencyCode": "NGN",
            "currencyDetails": {
                "countryIsoCode": "NG"
            },
            "cashout": {
                "amountBeforeFees": 31088,
                "amountAfterFees": 30000,
                "amountBeforeFeesUsd": 21.272315,
                "amountAfterFeesUsd": 20.527839,
                "chargedFees": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 777.2
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 310.88
                    }
                ],
                "chargedFeesUsd": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 0.531808
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 0.212723
                    }
                ],
                "totalChargedFees": 1088.08,
                "totalChargedFeesUsd": 0.744531,
                "exchangeRate": 1461.43,
                "exchangeRateAfterFees": 1514.4312,
                "chargedFeesPerRecipient": {
                    "platform": 777.2,
                    "merchant": 310.88
                },
                "chargedFeesPerRecipientUsd": {
                    "platform": 0.531808,
                    "merchant": 0.212723
                },
                "feeSettings": [
                    {
                        "id": "service_fee",
                        "recipient": "platform",
                        "type": "percentage",
                        "value": 2.5,
                        "min": 0,
                        "max": "Infinity"
                    },
                    {
                        "id": "merchant_fee",
                        "recipient": "merchant",
                        "type": "percentage",
                        "value": 1,
                        "min": 0,
                        "max": "Infinity"
                    }
                ]
            },
            "providedFieldsToCreateOrder": {
                "phoneNumber": "2348012345678",
                "bankCode": "1",
                "bankAccountNumber": "1234567890"
            }
        },
        "statusChangeLogs": [],
        "createdAt": "2025-12-01T13:31:28.372Z",
        "updatedAt": "2025-12-01T13:31:28.372Z",
        "expiresAt": "2025-12-01T14:01:28.156Z"
    }
}
```

{% endcode %}

From the response <mark style="color:yellow;">fieldsToConfirmOrder</mark> and <mark style="color:yellow;">transferInstructions</mark> we see that:

* to confirm the order we must provide <mark style="color:yellow;">blockchainTransactionHash</mark>
* a user must pay <mark style="color:yellow;">21.272315</mark>  Polygon USDT to this wallet <mark style="color:yellow;">0xdc9cbad0c43f912a66cd44cd22a15c04368e659f</mark> from this wallet <mark style="color:yellow;">0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20</mark>
  {% endstep %}

{% step %}

#### A user makes the crypto transfer with the exact amount to the specified wallet address.&#x20;

{% endstep %}

{% step %}

#### Call [Confirm order](/server-to-server/api-endpoints/confirm-order) with <mark style="color:yellow;">blockchainTransactionHash</mark> included:

{% code title="Example request" %}

```json
{
  "orderId": "692d98b0a60e21354637311a",
  "fieldsToConfirmOrder": {
     "blockchainTransactionHash": "your hash"
  }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### The system validates the deposit and processes payout.

{% endstep %}

{% step %}

#### Use [Get order](/server-to-server/api-endpoints/get-order) to track status until "<mark style="color:yellow;">payout\_successful</mark>".

{% endstep %}
{% endstepper %}


# Fiat to Merchant balance

Let’s do a NGN (fiat) deposit to merchant balance USD payout.&#x20;

{% stepper %}
{% step %}

#### First, call [Get available currencies](/server-to-server/api-endpoints/get-available-currencies) and assume you receive:

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "paymentChannels": [
      {
        "type": "bank",
        "transferTypes": [
          "manual",
          "redirect"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      },
      {
        "type": "airtime",
        "transferTypes": [
          "ussd"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel"
          },
          {
            "code": "GLO",
            "name": "Glo"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": false
      },
      {
        "type": "mobile_money",
        "transferTypes": [
          "stk_push",
          "otp_stk_push"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN Mobile Money"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel Money"
          },
          {
            "code": "GLO",
            "name": "Glo Mobile Money"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile Money"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "countryIsoCode": "NG",
    },
    "pairs": [
      "crypto",
      "merchant_balance"
    ]
  },
  {
    "currencyType": "merchant_balance",
    "currencyCode": "USD",
    "paymentChannels": [
      {
        "type": "merchant_balance",
        "transferTypes": [
          "manual"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "merchantName": "Fonbnk"
    },
    "pairs": [
      "fiat",
      "crypto"
    ]
  }
]
```

{% endcode %}

We see NGN supports deposit via bank/airtime/mobile money, and payout via bank/mobile money. Merchant balance supports both deposit and payout. So we can do Fiat → Merchant balance USD.
{% endstep %}

{% step %}

#### Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) with:

* depositPaymentChannel: "<mark style="color:yellow;">bank</mark>"
* depositCurrencyType: "<mark style="color:yellow;">fiat</mark>"
* depositCurrencyCode: "<mark style="color:yellow;">NGN</mark>"
* depositCountryIsoCode: "<mark style="color:yellow;">NG</mark>"
* payoutPaymentChannel: "<mark style="color:yellow;">merchant\_balance</mark>"
* payoutCurrencyType: "<mark style="color:yellow;">merchant\_balance</mark>"
* payoutCurrencyCode: "<mark style="color:yellow;">USD</mark>"

{% code title="Example response" %}

```json
{
  "deposit": {
    "min": 1523,
    "max": 761469,
    "minUsd": 1,
    "maxUsd": 500
  },
  "payout": {
    "min": 1,
    "max": 500,
    "minUsd": 1,
    "maxUsd": 500
  }
}
```

{% endcode %}

We see that the minimum deposit is 1523 NGN and the maximum is 761469 NGN, which corresponds to 1-500 USD limit for merchant balance.

Assume the merchant wants to receive 100 USD. Check the user’s tier using the [KYC requirements](/server-to-server/kyc-flow) flow before proceeding. In the sample sandbox rules, payouts of 100 USD or more trigger the <mark style="color:yellow;">advanced</mark> tier, so be sure the user has submitted and been approved for that document set.
{% endstep %}

{% step %}

#### Call [Create quote](/server-to-server/api-endpoints/create-quote):

{% code title="Example request" %}

```json
{
  "deposit": {
    "paymentChannel": "bank",
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "countryIsoCode": "NG"
  },
  "payout": {
    "paymentChannel": "merchant_balance",
    "currencyType": "merchant_balance",
    "currencyCode": "USD",
    "amount": 100
  }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692ee3e2f34fda2f154d4496",
    "quoteExpiresAt": "2025-12-02T13:34:34.797Z",
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "cashout": {
            "amountBeforeFees": 152206,
            "amountAfterFees": 146879,
            "amountBeforeFeesUsd": 103.626795,
            "amountAfterFeesUsd": 100,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 3805.15
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 1522.06
                }
            ],
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 2.59067
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 1.036268
                }
            ],
            "totalChargedFees": 5327.21,
            "totalChargedFeesUsd": 3.626938,
            "exchangeRate": 1468.79,
            "exchangeRateAfterFees": 1522.06,
            "chargedFeesPerRecipient": {
                "platform": 3805.15,
                "merchant": 1522.06
            },
            "chargedFeesPerRecipientUsd": {
                "platform": 2.59067,
                "merchant": 1.036268
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    },
                    {
                        "label": "Deposit underpayment (50%)",
                        "value": "deposit_underpayment"
                    },
                    {
                        "label": "Deposit overpayment (200%)",
                        "value": "deposit_overpayment"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "currencyDetails": {
            "merchantName": "bohdan test"
        },
        "cashout": {
            "amountBeforeFees": 100,
            "amountAfterFees": 100,
            "amountBeforeFeesUsd": 100,
            "amountAfterFeesUsd": 100,
            "chargedFees": [],
            "chargedFeesUsd": [],
            "totalChargedFees": 0,
            "totalChargedFeesUsd": 0,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1,
            "chargedFeesPerRecipient": {},
            "chargedFeesPerRecipientUsd": {},
            "feeSettings": []
        },
        "fieldsToCreateOrder": [
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "defaultValue": "payout_success",
                "required": false,
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed -> Refund success",
                        "value": "refund_success"
                    },
                    {
                        "label": "Payout failed -> Refund failed",
                        "value": "refund_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}

For a merchant to receive 100 USD, user must deposit 152206 NGN. Collect these fields:

* <mark style="color:yellow;">phoneNumber</mark>
* <mark style="color:yellow;">bankCode</mark> (from enum options)
* <mark style="color:yellow;">bankAccountNumber</mark>
* <mark style="color:yellow;">depositSandboxForcedFlow</mark> (sandbox optional field to simulate deposit success/failure/underpayment/overpayment)
* <mark style="color:yellow;">payoutSandboxForcedFlow</mark> (sandbox optional field to simulate payout success/failure)

> **Sandbox tip:** When you're testing in the sandbox environment you can set <mark style="color:yellow;">depositSandboxForcedFlow</mark> or <mark style="color:yellow;">payoutSandboxForcedFlow</mark> to force the platform to return success, failure, or other edge outcomes. This makes it easy to imitate failed flows without moving real funds.
> {% endstep %}

{% step %}

#### Create the order via [Create order:](/server-to-server/api-endpoints/create-order)

{% code title="Example request" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692ee3e2f34fda2f154d4496",
    "userCountryIsoCode": "NG",
    "userEmail": "tester+ng@fonbnk.com",
    "userIp": "223.134.123.12",
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG"
    },
    "payout": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "amount": 100
    },
    "fieldsToCreateOrder": {
        "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
        "phoneNumber": "2348012345678",
        "bankCode": "1",
        "bankAccountNumber": "1234567890"
    }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteUsed": true,
    "order": {
        "_id": "692ee4bba60e213546387b1e",
        "countryIsoCode": "NG",
        "userId": "686671f07730d8d1a9b2260a",
        "userEmail": "tester+ng@fonbnk.com",
        "status": "deposit_awaiting",
        "deposit": {
            "paymentChannel": "bank",
            "currencyType": "fiat",
            "currencyCode": "NGN",
            "currencyDetails": {
                "countryIsoCode": "NG"
            },
            "cashout": {
                "amountBeforeFees": 152206,
                "amountAfterFees": 146879,
                "amountBeforeFeesUsd": 103.626795,
                "amountAfterFeesUsd": 100,
                "chargedFees": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 3805.15
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 1522.06
                    }
                ],
                "chargedFeesUsd": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 2.59067
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 1.036268
                    }
                ],
                "totalChargedFees": 5327.21,
                "totalChargedFeesUsd": 3.626938,
                "exchangeRate": 1468.79,
                "exchangeRateAfterFees": 1522.06,
                "chargedFeesPerRecipient": {
                    "platform": 3805.15,
                    "merchant": 1522.06
                },
                "chargedFeesPerRecipientUsd": {
                    "platform": 2.59067,
                    "merchant": 1.036268
                },
                "feeSettings": [
                    {
                        "id": "service_fee",
                        "recipient": "platform",
                        "type": "percentage",
                        "value": 2.5,
                        "min": 0,
                        "max": "Infinity"
                    },
                    {
                        "id": "merchant_fee",
                        "recipient": "merchant",
                        "type": "percentage",
                        "value": 1,
                        "min": 0,
                        "max": "Infinity"
                    }
                ]
            },
            "providedFieldsToCreateOrder": {
                "phoneNumber": "2348012345678",
                "bankCode": "1",
                "bankAccountNumber": "1234567890"
            },
            "transferInstructions": {
                "type": "manual",
                "instructionsText": "It is a sandbox offer. Confirm the transfer from your side and system will automatically confirm the transfer within 1 minute.",
                "warningText": "Non-confirmed orders will be automatically canceled after 5 minutes.",
                "transferDetails": [
                    {
                        "id": "recipientBankName",
                        "label": "Bank name",
                        "value": "Sandbox Bank"
                    },
                    {
                        "id": "recipientBankAccountNumber",
                        "label": "Bank account number",
                        "value": "1073315490"
                    },
                    {
                        "id": "recipientBankAccountName",
                        "label": "Bank account name",
                        "value": "SANDY BOXERRITTO"
                    },
                    {
                        "id": "amountToSend",
                        "label": "Amount to send",
                        "value": "152206"
                    }
                ],
                "fieldsToConfirmOrder": []
            }
        },
        "payout": {
            "paymentChannel": "merchant_balance",
            "currencyType": "merchant_balance",
            "currencyCode": "USD",
            "currencyDetails": {
                "merchantName": "bohdan test"
            },
            "cashout": {
                "amountBeforeFees": 100,
                "amountAfterFees": 100,
                "amountBeforeFeesUsd": 100,
                "amountAfterFeesUsd": 100,
                "chargedFees": [],
                "chargedFeesUsd": [],
                "totalChargedFees": 0,
                "totalChargedFeesUsd": 0,
                "exchangeRate": 1,
                "exchangeRateAfterFees": 1,
                "chargedFeesPerRecipient": {},
                "chargedFeesPerRecipientUsd": {
                    "platform": 2.59067,
                    "merchant": 1.036268
                },
                "feeSettings": []
            },
            "providedFieldsToCreateOrder": {}
        },
        "statusChangeLogs": [],
        "createdAt": "2025-12-02T13:08:11.013Z",
        "updatedAt": "2025-12-02T13:08:11.013Z",
        "expiresAt": "2025-12-02T13:13:10.972Z"
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### A user makes the transfer with the exact amount and reference.&#x20;

{% endstep %}

{% step %}

#### Call [Confirm order](/server-to-server/api-endpoints/confirm-order) if no extra fields are required:

```json
{
  "orderId": "692ee4bba60e213546387b1e"
}
```

{% endstep %}

{% step %}

#### The system validates the deposit and processes payout.&#x20;

{% endstep %}

{% step %}

#### Use [Get order](/server-to-server/api-endpoints/get-order) to track status until " <mark style="color:yellow;">payout\_successful</mark>".

{% endstep %}
{% endstepper %}


# Merchant balance to Fiat

Let’s do a merchant balance USD deposit to NGN bank payout.&#x20;

{% stepper %}
{% step %}

#### First, call the [Get merchant balance](/server-to-server/api-endpoints/merchant-balance/get-merchant-balances) endpoint to understand how much money is at your disposal.

```json
{
    "USD": 761
}
```

{% endstep %}

{% step %}

#### Call [Get currencies](https://github.com/fonbnk/docs/blob/main/api-v2.md#get-currencies):&#x20;

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "paymentChannels": [
      {
        "type": "bank",
        "transferTypes": [
          "manual",
          "redirect"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      },
      {
        "type": "airtime",
        "transferTypes": [
          "ussd"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel"
          },
          {
            "code": "GLO",
            "name": "Glo"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": false
      },
      {
        "type": "mobile_money",
        "transferTypes": [
          "stk_push",
          "otp_stk_push"
        ],
        "carriers": [
          {
            "code": "MTN",
            "name": "MTN Mobile Money"
          },
          {
            "code": "AIRTEL",
            "name": "Airtel Money"
          },
          {
            "code": "GLO",
            "name": "Glo Mobile Money"
          },
          {
            "code": "9MOBILE",
            "name": "9Mobile Money"
          }
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "countryIsoCode": "NG",
    },
    "pairs": [
    "crypto",
      "merchant_balance"
    ]
  },
  {
    "currencyType": "merchant_balance",
    "currencyCode": "USD",
    "paymentChannels": [
      {
        "type": "merchant_balance",
        "transferTypes": [
          "manual"
        ],
        "isDepositAllowed": true,
        "isPayoutAllowed": true
      }
    ],
    "currencyDetails": {
      "merchantName": "Fonbnk"
    },
    "pairs": [
      "fiat",
      "crypto"
    ]
  }
]
```

{% endcode %}

We see that NGN supports deposit and payout. Merchant balance supports both deposit and payout too. So we can do NGN -> Merchant balance USD.
{% endstep %}

{% step %}

#### Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) with:

* depositPaymentChannel: "<mark style="color:yellow;">merchant\_balance</mark>"
* depositCurrencyType: "<mark style="color:yellow;">merchant\_balance</mark>"
* depositCurrencyCode: "<mark style="color:yellow;">USD</mark>"
* payoutPaymentChannel: "<mark style="color:yellow;">bank</mark>"
* payoutCurrencyType: "<mark style="color:yellow;">fiat</mark>"
* payoutCurrencyCode: "<mark style="color:yellow;">NGN</mark>"

{% code title="Example response" overflow="wrap" %}

```json
{
    "deposit": {
        "min": 1,
        "max": 500,
        "minUsd": 1,
        "maxUsd": 500
    },
    "payout": {
        "min": 1412,
        "max": 705782,
        "minUsd": 1,
        "maxUsd": 500
    }
}
```

{% endcode %}

We see that the minimum deposit is 1 USD and the maximum is 500 USD, a user can receive from 1412 NGN to 705782 NGN.

Assume the merchant wants to send 100 USD. Reuse the [KYC requirements](/server-to-server/kyc-flow) checklist to confirm the customer has passed the appropriate tier. With the example rules, this amount again requires the <mark style="color:yellow;">advanced</mark> document set before you continue.
{% endstep %}

{% step %}

#### Call [Create quote](/server-to-server/api-endpoints/create-quote):

{% code title="Example request" %}

```json
{
  "deposit": {
    "paymentChannel": "merchant_balance",
    "currencyType": "merchant_balance",
    "currencyCode": "USD",
    "amount": 100
  },
  "payout": {
    "paymentChannel": "bank",
    "currencyType": "fiat",
    "currencyCode": "NGN",
    "countryIsoCode": "NG"
  }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692ef2bddebffd422445f0a7",
    "quoteExpiresAt": "2025-12-02T14:37:57.910Z",
    "deposit": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "currencyDetails": {
            "merchantName": "tester"
        },
        "cashout": {
            "amountBeforeFees": 100,
            "amountAfterFees": 100,
            "chargedFees": [],
            "totalChargedFees": 0,
            "chargedFeesPerRecipient": {},
            "amountBeforeFeesUsd": 100,
            "amountAfterFeesUsd": 100,
            "chargedFeesUsd": [],
            "totalChargedFeesUsd": 0,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1,
            "chargedFeesPerRecipientUsd": {},
            "feeSettings": []
        },
        "fieldsToCreateOrder": [
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    },
                    {
                        "label": "Deposit underpayment (50%)",
                        "value": "deposit_underpayment"
                    },
                    {
                        "label": "Deposit overpayment (200%)",
                        "value": "deposit_overpayment"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "cashout": {
            "amountBeforeFees": 146276,
            "amountAfterFees": 141156,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 3656.9
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 1462.76
                }
            ],
            "totalChargedFees": 5119.66,
            "chargedFeesPerRecipient": {
                "platform": 3656.9,
                "merchant": 1462.76
            },
            "amountBeforeFeesUsd": 100,
            "amountAfterFeesUsd": 96.499768,
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 2.5
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 1
                }
            ],
            "totalChargedFeesUsd": 3.5,
            "exchangeRate": 1462.76,
            "exchangeRateAfterFees": 1515.8171,
            "chargedFeesPerRecipientUsd": {
                "platform": 2.5,
                "merchant": 1
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "required": false,
                "defaultValue": "payout_success",
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed",
                        "value": "payout_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}

A user will receive 141156 NGN for 100 USD of merchant balance. Collect these fields:

* <mark style="color:yellow;">phoneNumber</mark>
* <mark style="color:yellow;">bankCode</mark>
* <mark style="color:yellow;">bankAccountNumber</mark>
* <mark style="color:yellow;">depositSandboxForcedFlow</mark> (sandbox optional field to simulate deposit success/failure/underpayment/overpayment)
* <mark style="color:yellow;">payoutSandboxForcedFlow</mark> (sandbox optional field to simulate payout success/failure)
  {% endstep %}

{% step %}

#### Create the order via [Create order](/server-to-server/api-endpoints/create-order):

{% code title="Example request" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "692ef2bddebffd422445f0a7",
    "userCountryIsoCode": "NG",
    "userEmail": "tester+ng@fonbnk.com",
    "userIp": "223.134.123.12",
    "deposit": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "amount": 100
    },
    "payout": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG"
    },
    "fieldsToCreateOrder": {
        "phoneNumber": "2348012345678",
        "bankCode": "1",
        "bankAccountNumber": "1234567890"
    }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteUsed": true,
    "order": {
        "_id": "692ef363ee270426b27cd0b6",
        "countryIsoCode": "NG",
        "userId": "686671f07730d8d1a9b2260a",
        "userEmail": "tester+ng@fonbnk.com",
        "status": "deposit_awaiting",
        "deposit": {
            "paymentChannel": "merchant_balance",
            "currencyType": "merchant_balance",
            "currencyCode": "USD",
            "currencyDetails": {
                "merchantName": "bohdan test"
            },
            "cashout": {
                "amountBeforeFees": 100,
                "amountAfterFees": 100,
                "chargedFees": [],
                "totalChargedFees": 0,
                "chargedFeesPerRecipient": {},
                "amountBeforeFeesUsd": 100,
                "amountAfterFeesUsd": 100,
                "chargedFeesUsd": [],
                "totalChargedFeesUsd": 0,
                "exchangeRate": 1,
                "exchangeRateAfterFees": 1,
                "chargedFeesPerRecipientUsd": {},
                "feeSettings": []
            },
            "providedFieldsToCreateOrder": {},
            "transferInstructions": {
                "type": "manual",
                "transferDetails": [],
                "instructionsText": "",
                "fieldsToConfirmOrder": []
            }
        },
        "payout": {
            "paymentChannel": "bank",
            "currencyType": "fiat",
            "currencyCode": "NGN",
            "currencyDetails": {
                "countryIsoCode": "NG"
            },
            "cashout": {
                "amountBeforeFees": 146276,
                "amountAfterFees": 141156,
                "chargedFees": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 3656.9
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 1462.76
                    }
                ],
                "totalChargedFees": 5119.66,
                "chargedFeesPerRecipient": {
                    "platform": 3656.9,
                    "merchant": 1462.76
                },
                "amountBeforeFeesUsd": 100,
                "amountAfterFeesUsd": 96.499768,
                "chargedFeesUsd": [
                    {
                        "id": "service_fee",
                        "type": "percentage",
                        "recipient": "platform",
                        "amount": 2.5
                    },
                    {
                        "id": "merchant_fee",
                        "type": "percentage",
                        "recipient": "merchant",
                        "amount": 1
                    }
                ],
                "totalChargedFeesUsd": 3.5,
                "exchangeRate": 1462.76,
                "exchangeRateAfterFees": 1515.8171,
                "chargedFeesPerRecipientUsd": {
                    "platform": 2.5,
                    "merchant": 1
                },
                "feeSettings": [
                    {
                        "id": "service_fee",
                        "recipient": "platform",
                        "type": "percentage",
                        "value": 2.5,
                        "min": 0,
                        "max": "Infinity"
                    },
                    {
                        "id": "merchant_fee",
                        "recipient": "merchant",
                        "type": "percentage",
                        "value": 1,
                        "min": 0,
                        "max": "Infinity"
                    }
                ]
            },
            "providedFieldsToCreateOrder": {
                "phoneNumber": "2348012345678",
                "bankCode": "1",
                "bankAccountNumber": "1234567890"
            }
        },
        "statusChangeLogs": [],
        "createdAt": "2025-12-02T14:10:43.333Z",
        "updatedAt": "2025-12-02T14:10:43.333Z",
        "expiresAt": "2025-12-02T17:10:43.294Z"
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### Call [Confirm order](/server-to-server/api-endpoints/confirm-order):

Its a merchant balance deposit, so no fields are required, your balance will be debited automatically.

Example request

```
{
  "orderId": "692ef363ee270426b27cd0b6"
}
```

{% endstep %}

{% step %}

#### The system validates the deposit and processes payout.&#x20;

{% endstep %}

{% step %}

#### Use [Get order](/server-to-server/api-endpoints/get-order) to track status until " <mark style="color:yellow;">payout\_successful</mark>".

{% endstep %}
{% endstepper %}


# Crypto to Merchant balance

Let’s do a NGN (fiat) deposit to merchant balance USD payout.&#x20;

{% stepper %}
{% step %}

#### First, call [Get available currencies](/server-to-server/api-endpoints/get-available-currencies) and assume you receive:

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
[
  {
    currencyType: 'crypto',
    currencyCode: 'POLYGON_USDT',
    paymentChannels: [
      {
        type: 'crypto',
        transferTypes: ['manual'],
        isDepositAllowed: true,
        isPayoutAllowed: true,
      },
    ],
    currencyDetails: {
      network: 'POLYGON',
      asset: 'USDT',
      contractAddress: '0x3b3a06b48119c035a2e86afdb69d9ad930643b3d',
    },
    pairs: ['fiat', 'merchant_balance'],
  },
  {
    currencyType: 'merchant_balance',
    currencyCode: 'USD',
    paymentChannels: [
      {
        type: 'merchant_balance',
        transferTypes: ['manual'],
        isDepositAllowed: true,
        isPayoutAllowed: true,
      },
    ],
    currencyDetails: {
      merchantName: 'Fonbnk',
    },
    pairs: ['fiat', 'crypto'],
  },
];
```

{% endcode %}

We see POLYGON\_USDT supports both deposits and payouts. Merchant balance supports both deposit and payout. So we can do Crypto → Merchant balance USD.
{% endstep %}

{% step %}

#### Call [Get order limits](/server-to-server/api-endpoints/get-order-limits) with:

* depositPaymentChannel: "<mark style="color:yellow;">crypto</mark>"
* depositCurrencyType: "<mark style="color:yellow;">crypto</mark>"
* depositCurrencyCode: "<mark style="color:yellow;">POLYGON\_USDT</mark>"
* payoutPaymentChannel: "<mark style="color:yellow;">merchant\_balance</mark>"
* payoutCurrencyType: "<mark style="color:yellow;">merchant\_balance</mark>"
* payoutCurrencyCode: "<mark style="color:yellow;">USD</mark>"

{% code title="Example response" %}

```json
{
    "deposit": {
        "min": 1,
        "max": 500,
        "minUsd": 1,
        "maxUsd": 500,
        "supportsDecimals": true,
        "step": 0.000001
    },
    "payout": {
        "min": 1,
        "max": 500,
        "minUsd": 1,
        "maxUsd": 500,
        "supportsDecimals": true,
        "step": 0.01
    }
}
```

{% endcode %}

We see that the minimum deposit is 1 POLYGON\_USDT and the maximum is 500 POLYGON\_USDT, which corresponds to 1-500 USD limit for merchant balance.

Assume the merchant wants to receive 100 USD. Check the user’s tier using the [KYC requirements](/server-to-server/kyc-flow) flow before proceeding. In the sample sandbox rules, payouts of 100 USD or more trigger the <mark style="color:yellow;">advanced</mark> tier, so be sure the user has submitted and been approved for that document set.
{% endstep %}

{% step %}

#### Call [Create quote](/server-to-server/api-endpoints/create-quote):

{% code title="Example request" %}

```json
{
  "deposit": {
    "paymentChannel": "crypto",
    "currencyType": "crypto",
    "currencyCode": "POLYGON_USDT"
  },
  "payout": {
    "paymentChannel": "merchant_balance",
    "currencyType": "merchant_balance",
    "currencyCode": "USD",
    "amount": 100
  }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "69c5443c5ea688026a418ca2",
    "quoteExpiresAt": "2026-03-26T15:05:40.842Z",
    "deposit": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "cashout": {
            "amountBeforeFees": 100,
            "amountAfterFees": 100,
            "amountBeforeFeesUsd": 100,
            "amountAfterFeesUsd": 100,
            "chargedFees": [],
            "chargedFeesUsd": [],
            "totalChargedFees": 0,
            "totalChargedFeesUsd": 0,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1,
            "chargedFeesPerRecipient": {},
            "chargedFeesPerRecipientUsd": {},
            "feeSettings": []
        },
        "fieldsToCreateOrder": [
            {
                "key": "blockchainWalletAddress",
                "type": "string",
                "label": "Your wallet address",
                "required": false
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "currencyDetails": {
            "merchantName": "bohdan test"
        },
        "cashout": {
            "amountBeforeFees": 100,
            "amountAfterFees": 100,
            "amountBeforeFeesUsd": 100,
            "amountAfterFeesUsd": 100,
            "chargedFees": [],
            "chargedFeesUsd": [],
            "totalChargedFees": 0,
            "totalChargedFeesUsd": 0,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1,
            "chargedFeesPerRecipient": {},
            "chargedFeesPerRecipientUsd": {},
            "feeSettings": []
        },
        "fieldsToCreateOrder": [
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "defaultValue": "payout_success",
                "required": false,
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed -> Refund success",
                        "value": "refund_success"
                    },
                    {
                        "label": "Payout failed -> Refund failed",
                        "value": "refund_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}

For a merchant to receive 100 USD, user must deposit 100 POLYGON\_USDT. Collect these fields:

* <mark style="color:yellow;">blockchainWalletAddress</mark>
* <mark style="color:yellow;">depositSandboxForcedFlow</mark> (sandbox optional field to simulate deposit success/failure/underpayment/overpayment)
* <mark style="color:yellow;">payoutSandboxForcedFlow</mark> (sandbox optional field to simulate payout success/failure)

> **Sandbox tip:** When you're testing in the sandbox environment you can set <mark style="color:yellow;">depositSandboxForcedFlow</mark> or <mark style="color:yellow;">payoutSandboxForcedFlow</mark> to force the platform to return success, failure, or other edge outcomes. This makes it easy to imitate failed flows without moving real funds.
> {% endstep %}

{% step %}

#### Create the order via [Create order:](/server-to-server/api-endpoints/create-order)

{% code title="Example request" overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "69c5443c5ea688026a418ca2",
    "userCountryIsoCode": "NG",
    "userEmail": "tester+ng@fonbnk.com",
    "userIp": "223.134.123.12",
    "deposit": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "amount": 100
    },
    "payout": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD"
    },
    "fieldsToCreateOrder": {
        "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
    }
}
```

{% endcode %}

{% code title="Example response" overflow="wrap" expandable="true" %}

```json
{
    "quoteUsed": true,
    "order": {
        "_id": "69c545ea57cb634f272a57f2",
        "countryIsoCode": "NG",
        "userId": "69c54529ca181e1811eeb37b",
        "userEmail": "tester+ng@fonbnk.com",
        "status": "deposit_awaiting",
        "deposit": {
            "paymentChannel": "crypto",
            "currencyType": "crypto",
            "currencyCode": "POLYGON_USDT",
            "currencyDetails": {
                "network": "POLYGON",
                "asset": "USDT",
                "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
            },
            "cashout": {
                "amountBeforeFees": 100,
                "amountAfterFees": 100,
                "chargedFees": [],
                "totalChargedFees": 0,
                "chargedFeesPerRecipient": {},
                "amountBeforeFeesUsd": 100,
                "amountAfterFeesUsd": 100,
                "chargedFeesUsd": [],
                "totalChargedFeesUsd": 0,
                "exchangeRate": 1,
                "exchangeRateAfterFees": 1,
                "chargedFeesPerRecipientUsd": {},
                "feeSettings": []
            },
            "providedFieldsToCreateOrder": {
                "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
            },
            "transferInstructions": {
                "type": "manual",
                "instructionsText": "Please send the exact amount of crypto to the address below. Make sure to send only USDT on the Polygon network. Sending any other assets or using a different network may result in loss of funds.",
                "transferDetails": [
                    {
                        "id": "recipientWalletAddress",
                        "label": "Wallet address to send",
                        "value": "0xdc9cbad0c43f912a66cd44cd22a15c04368e659f"
                    },
                    {
                        "id": "senderWalletAddress",
                        "label": "Your wallet address",
                        "value": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
                    },
                    {
                        "id": "amountToSend",
                        "label": "Amount to send",
                        "value": "100"
                    },
                    {
                        "id": "cryptoTransactionRequestAdditionalData",
                        "label": "Crypto transaction additional data",
                        "value": ""
                    }
                ],
                "fieldsToConfirmOrder": [
                    {
                        "key": "blockchainTransactionHash",
                        "type": "string",
                        "label": "Transaction hash",
                        "required": true
                    }
                ]
            },
            "formattedUserFieldsToCreateOrder": [
                {
                    "key": "blockchainWalletAddress",
                    "label": "Your wallet address",
                    "value": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
                    "type": "string"
                },
                {
                    "key": "depositSandboxForcedFlow",
                    "label": "Sandbox deposit forced flow",
                    "value": null,
                    "type": "enum"
                }
            ]
        },
        "payout": {
            "paymentChannel": "merchant_balance",
            "currencyType": "merchant_balance",
            "currencyCode": "USD",
            "currencyDetails": {
                "merchantName": "bohdan test"
            },
            "cashout": {
                "amountBeforeFees": 100,
                "amountAfterFees": 100,
                "chargedFees": [],
                "totalChargedFees": 0,
                "chargedFeesPerRecipient": {},
                "amountBeforeFeesUsd": 100,
                "amountAfterFeesUsd": 100,
                "chargedFeesUsd": [],
                "totalChargedFeesUsd": 0,
                "exchangeRate": 1,
                "exchangeRateAfterFees": 1,
                "chargedFeesPerRecipientUsd": {},
                "feeSettings": []
            },
            "providedFieldsToCreateOrder": {},
            "formattedUserFieldsToCreateOrder": [
                {
                    "key": "payoutSandboxForcedFlow",
                    "label": "Sandbox payout forced flow",
                    "value": null,
                    "type": "enum"
                }
            ]
        },
        "statusChangeLogs": [],
        "createdAt": "2026-03-26T14:42:51.252Z",
        "updatedAt": "2026-03-26T14:42:51.252Z",
        "expiresAt": "2026-03-26T15:12:50.857Z"
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### A user makes the transfer with the exact amount and reference.&#x20;

{% endstep %}

{% step %}

#### Call [Confirm order](/server-to-server/api-endpoints/confirm-order) with a transaction hash:

```json
{
  "orderId": "692ee4bba60e213546387b1e",
  "fieldsToConfirmOrder": {
    "blockchainTransactionHash": "...hash"
  }
}
```

{% endstep %}

{% step %}

#### The system validates the deposit and processes payout.&#x20;

{% endstep %}

{% step %}

#### Use [Get order](/server-to-server/api-endpoints/get-order) to track status until " <mark style="color:yellow;">payout\_successful</mark>".

{% endstep %}
{% endstepper %}


# Servers

#### API servers[​](https://docs.fonbnk.com/docs/offramp/merchant-api#api-servers)​ <a href="#api-servers" id="api-servers"></a>

|            |                                                                     |
| ---------- | ------------------------------------------------------------------- |
| Sandbox    | ​[https://sandbox-api.fonbnk.com](https://sandbox-api.fonbnk.com/)​ |
| Production | ​[https://api.fonbnk.com](https://api.fonbnk.com/)​                 |


# Signing requests

### Request Authentication[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#request-authentication) <a href="#request-authentication" id="request-authentication"></a>

All requests should be signed using a HMAC256 algorithm and provided `clientId` and `clientSecret`.

### How to get the signature of the request?[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#how-to-get-the-signature-of-the-request) <a href="#how-to-get-the-signature-of-the-request" id="how-to-get-the-signature-of-the-request"></a>

1. Generate a timestamp (Epoch Unix Timestamp) in milliseconds
2. Concatenate the timestamp and the endpoint that is called `{timestamp}:{endpoint}`
3. Decode the base64 encoded clientSecret
4. Compute the SHA256 hash of the concatenated string. Use decoded clientSecret as a key. Convert the result to base64
5. Add the clientId, signature, and timestamp to HTTP headers

The following pseudocode example demonstrates and explains how to sign a request

{% code overflow="wrap" %}

```
timestamp = CurrentTimestamp();
stringToSign = timestamp + ":" + endpoint;
signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( clientSecret ), UTF8 ( concatenatedString ) ) );
```

{% endcode %}

## Request examples <a href="#request-examples" id="request-examples"></a>

The following examples send HTTP request to [Get order limits](/server-to-server/api-endpoints/get-order-limits) API endpoint:

{% tabs %}
{% tab title="Typescript" %}
{% code overflow="wrap" %}

```typescript
import crypto from 'crypto';
const BASE_URL = 'https://api.fonbnk.com';
const ENDPOINT = '/api/v2/order-limits;
const CLIENT_ID = '';
const CLIENT_SECRET = '';

const generateSignature = ({
  clientSecret,
  timestamp,
  endpoint,
}: {
  clientSecret: string;
  timestamp: string;
  endpoint: string;
}) => {
  let hmac = crypto.createHmac('sha256', Buffer.from(clientSecret, 'base64'));
  let stringToSign = `${timestamp}:${endpoint}`;
  hmac.update(stringToSign);
  return hmac.digest('base64');
};
const main = async () => {
  const timestamp = new Date().getTime();
  const queryParams = new URLSearchParams({
    depositPaymentChannel: 'bank',
    depositCurrencyType: 'fiat',
    depositCurrencyCode: 'NGN',
    depositCountryIsoCode: 'NG',
    payoutPaymentChannel: 'crypto',
    payoutCurrencyType: 'crypto',
    payoutCurrencyCode: 'CELO_USDT',
  });
  const endpoint = `${ENDPOINT}?${queryParams.toString()}`;
  const signature = generateSignature({
    clientSecret: CLIENT_SECRET,
    timestamp: timestamp.toString(),
    endpoint,
  });
  const headers = {
    'Content-Type': 'application/json',
    'x-client-id': CLIENT_ID,
    'x-timestamp': timestamp.toString(),
    'x-signature': signature,
  };
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    method: 'GET',
    headers,
  });
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
};

main().catch(console.error);

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```python
import hmac
import base64
import time
import requests
from urllib.parse import urlencode

BASE_URL = 'https://api.fonbnk.com'
ENDPOINT = '/api/v2/order-limits'
CLIENT_ID = ''
CLIENT_SECRET = ''

def pad_base64(base64_string):
    return base64_string + '=' * (-len(base64_string) % 4)

def generate_signature(client_secret, timestamp, endpoint):
    client_secret_padded = pad_base64(client_secret)
    hmac_obj = hmac.new(base64.b64decode(client_secret_padded), f'{timestamp}:{endpoint}'.encode('utf-8'), 'sha256')
    return base64.b64encode(hmac_obj.digest()).decode('utf-8')

def main():
    timestamp = str(int(time.time() * 1000))
    query_params = {
        'depositPaymentChannel': 'bank',
        'depositCurrencyType': 'fiat',
        'depositCurrencyCode': 'NGN',
        'depositCountryIsoCode': 'NG',
        'payoutPaymentChannel': 'crypto',
        'payoutCurrencyType': 'crypto',
        'payoutCurrencyCode': 'CELO_USDT',
    }
    endpoint = f"{ENDPOINT}?{urlencode(query_params)}"
    signature = generate_signature(CLIENT_SECRET, timestamp, endpoint)
    headers = {
        'Content-Type': 'application/json',
        'x-client-id': CLIENT_ID,
        'x-timestamp': timestamp,
        'x-signature': signature,
    }
    response = requests.get(f"{BASE_URL}{endpoint}", headers=headers)
    data = response.json()
    print(data)

if __name__ == "__main__":
    main()

```

{% endcode %}
{% endtab %}

{% tab title="GO" %}
{% code overflow="wrap" %}

```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	BASE_URL      = "https://api.fonbnk.com"
	ENDPOINT      = "/api/v2/order-limits"
	CLIENT_ID     = ""
	CLIENT_SECRET = ""
)

func padBase64(base64String string) string {
	return base64String + strings.Repeat("=", (4-len(base64String)%4)%4)
}

func generateSignature(clientSecret, timestamp, endpoint string) (string, error) {
	clientSecretPadded := padBase64(clientSecret)
	decodedSecret, err := base64.StdEncoding.DecodeString(clientSecretPadded)
	if err != nil {
		return "", err
	}
	message := fmt.Sprintf("%s:%s", timestamp, endpoint)
	h := hmac.New(sha256.New, decodedSecret)
	h.Write([]byte(message))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

func main() {
	timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
	queryParams := url.Values{
		"depositPaymentChannel": {"bank"},
    "depositCurrencyType": {"fiat"},
    "depositCurrencyCode": {"NGN"},
    "depositCountryIsoCode": {"NG"},
    "payoutPaymentChannel": {"crypto"},
    "payoutCurrencyType": {"crypto"},
    "payoutCurrencyCode": {"CELO_USDT"},
	}
	endpoint := fmt.Sprintf("%s?%s", ENDPOINT, queryParams.Encode())
	signature, err := generateSignature(CLIENT_SECRET, timestamp, endpoint)
	if err != nil {
		fmt.Println("Error generating signature:", err)
		return
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", BASE_URL+endpoint, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-client-id", CLIENT_ID)
	req.Header.Set("x-timestamp", timestamp)
	req.Header.Set("x-signature", signature)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making request:", err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	var data map[string]interface{}
	if err := json.Unmarshal(body, &data); err != nil {
		fmt.Println("Error unmarshalling response:", err)
		return
	}

	fmt.Println(data)
}

```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code overflow="wrap" %}

```php
<?php

define('BASE_URL', 'https://api.fonbnk.com');
define('ENDPOINT', '/api/v2/order-limits');
define('CLIENT_ID', '');
define('CLIENT_SECRET', '');

function pad_base64($base64_string) {
    return $base64_string . str_repeat('=', (4 - strlen($base64_string) % 4) % 4);
}

function generate_signature($client_secret, $timestamp, $endpoint) {
    $client_secret_padded = pad_base64($client_secret);
    $hmac = hash_hmac('sha256', "$timestamp:$endpoint", base64_decode($client_secret_padded), true);
    return base64_encode($hmac);
}

function main() {
    $timestamp = (string) round(microtime(true) * 1000);
    $query_params = [
        'depositPaymentChannel' => 'bank',
        'depositCurrencyType' => 'fiat',
        'depositCurrencyCode' => 'NGN',
        'depositCountryIsoCode' => 'NG',
        'payoutPaymentChannel' => 'crypto',
        'payoutCurrencyType' => 'crypto',
        'payoutCurrencyCode' => 'CELO_USDT',
    ];
    $endpoint = ENDPOINT . '?' . http_build_query($query_params);
    $signature = generate_signature(CLIENT_SECRET, $timestamp, $endpoint);
    $headers = [
        'Content-Type: application/json',
        'x-client-id: ' . CLIENT_ID,
        'x-timestamp: ' . $timestamp,
        'x-signature: ' . $signature,
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, BASE_URL . $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    print_r($data);
}

main();
?>
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    private static final String BASE_URL = "https://api.fonbnk.com";
    private static final String ENDPOINT = "/api/v2/order-limits";
    private static final String CLIENT_ID = "";
    private static final String CLIENT_SECRET = "";

    public static void main(String[] args) throws Exception {
        long timestamp = System.currentTimeMillis();
        Map<String, String> queryParams = new HashMap<>();
        
        queryParams.put("depositPaymentChannel", "bank");
        queryParams.put("depositCurrencyType", "fiat");
        queryParams.put("depositCurrencyCode", "NGN");
        queryParams.put("depositCountryIsoCode", "NG");
        queryParams.put("payoutPaymentChannel", "crypto");
        queryParams.put("payoutCurrencyType", "crypto");
        queryParams.put("payoutCurrencyCode", "CELO_USDT");
        
        String endpoint = ENDPOINT + "?" + getQuery(queryParams);
        String signature = generateSignature(CLIENT_SECRET, String.valueOf(timestamp), endpoint);

        URL url = new URL(BASE_URL + endpoint);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("x-client-id", CLIENT_ID);
        connection.setRequestProperty("x-timestamp", String.valueOf(timestamp));
        connection.setRequestProperty("x-signature", signature);

        Scanner scanner = new Scanner(connection.getInputStream());
        String response = scanner.useDelimiter("\\A").next();
        System.out.println(response);
        scanner.close();
    }

    private static String padBase64(String base64String) {
        return base64String + "=".repeat((4 - base64String.length() % 4) % 4);
    }

    private static String generateSignature(String clientSecret, String timestamp, String endpoint) throws Exception {
        String clientSecretPadded = padBase64(clientSecret);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(clientSecretPadded), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        String data = timestamp + ":" + endpoint;
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hmacBytes);
    }

    private static String getQuery(Map<String, String> params) throws Exception {
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (result.length() > 0) {
                result.append("&");
            }
            result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            result.append("=");
            result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }
        return result.toString();
    }
}
```

{% endtab %}

{% tab title="Dart" %}

```dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

void main() async {
  const String BASE_URL = "https://api.fonbnk.com";
  const String ENDPOINT = "/api/v2/order-limits";
  const String CLIENT_ID = "";
  const String CLIENT_SECRET = "";

  // Get the current timestamp in milliseconds
  int timestamp = DateTime.now().millisecondsSinceEpoch;

  // Create query parameters
  Map<String, String> queryParams = {
     'depositPaymentChannel': 'bank',
     'depositCurrencyType': 'fiat',
     'depositCurrencyCode': 'NGN',
     'depositCountryIsoCode': 'NG',
     'payoutPaymentChannel': 'crypto',
     'payoutCurrencyType': 'crypto',
     'payoutCurrencyCode': 'CELO_USDT',
  };

  // Generate the query string
  String queryString = getQuery(queryParams);

  // Create the endpoint with query parameters
  String endpoint = ENDPOINT + "?" + queryString;

  // Generate the signature
  String signature = generateSignature(CLIENT_SECRET, timestamp.toString(), endpoint);

  // Build the URL
  String url = BASE_URL + endpoint;

  // Set up the HTTP GET request
  var headers = {
    "Content-Type": "application/json",
    "x-client-id": CLIENT_ID,
    "x-timestamp": timestamp.toString(),
    "x-signature": signature,
  };

  // Send the GET request
  var response = await http.get(Uri.parse(url), headers: headers);

  // Print the response body
  print(response.body);
}

String getQuery(Map<String, String> params) {
  return params.entries
      .map((entry) =>
  Uri.encodeQueryComponent(entry.key) + "=" + Uri.encodeQueryComponent(entry.value))
      .join("&");
}

String generateSignature(String clientSecret, String timestamp, String endpoint) {
  // Use the custom lenient Base64 decoder
  List<int> secretKey = lenientBase64Decode(clientSecret);

  Hmac hmac = Hmac(sha256, secretKey);
  String data = '$timestamp:$endpoint';
  Digest digest = hmac.convert(utf8.encode(data));

  // Encode the signature using Base64
  String signature = base64Encode(digest.bytes);
  return signature;
}

List<int> lenientBase64Decode(String input) {
  // Base64 index table
  const String base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  // Remove all characters that are not in the Base64 alphabet
  String sanitizedInput = input.replaceAll(RegExp(r'[^A-Za-z0-9+/]'), '');

  // Map each character to its Base64 index
  List<int> buffer = [];
  int bits = 0;
  int bitsCount = 0;

  for (int i = 0; i < sanitizedInput.length; i++) {
    int val = base64Chars.indexOf(sanitizedInput[i]);
    if (val < 0) {
      // Skip invalid characters
      continue;
    }
    bits = (bits << 6) | val;
    bitsCount += 6;
    if (bitsCount >= 8) {
      bitsCount -= 8;
      int byte = (bits >> bitsCount) & 0xFF;
      buffer.add(byte);
    }
  }

  return buffer;
}
```

{% endtab %}

{% tab title="Elixir" %}

```elixir
Mix.install([
  {:httpoison, "~> 1.8"},
  {:jason, "~> 1.4"}
])

defmodule FonbnkClient do
  @moduledoc """
  A client for interacting with the Fonbnk API.
  """

  @base_url "https://api.fonbnk.com"
  @endpoint "/api/v2/order-limits"
  @client_id ""
  @client_secret ""

  def pad_base64(base64_string) do
    pad_length = Integer.mod(-String.length(base64_string), 4)
    base64_string <> String.duplicate("=", pad_length)
  end

  def generate_signature(client_secret, timestamp, endpoint) do
    client_secret_padded = pad_base64(client_secret)
    {:ok, client_secret_decoded} = Base.decode64(client_secret_padded)
    message = "#{timestamp}:#{endpoint}"
    hmac = :crypto.mac(:hmac, :sha256, client_secret_decoded, message)
    Base.encode64(hmac)
  end

  def main do
    timestamp = :os.system_time(:millisecond) |> Integer.to_string()
    query_params = %{
        'depositPaymentChannel' => 'bank',
        'depositCurrencyType' => 'fiat',
        'depositCurrencyCode' => 'NGN',
        'depositCountryIsoCode' => 'NG',
        'payoutPaymentChannel' => 'crypto',
        'payoutCurrencyType' => 'crypto',
        'payoutCurrencyCode' => 'CELO_USDT',
    }

    encoded_query = URI.encode_query(query_params)
    endpoint = @endpoint <> "?" <> encoded_query
    signature = generate_signature(@client_secret, timestamp, endpoint)

    headers = [
      {"Content-Type", "application/json"},
      {"x-client-id", @client_id},
      {"x-timestamp", timestamp},
      {"x-signature", signature}
    ]

    url = @base_url <> endpoint

    case HTTPoison.get(url, headers) do
      {:ok, %HTTPoison.Response{body: body, status_code: code}} when code in 200..299 ->
        data = Jason.decode!(body)
        IO.inspect(data)

      {:ok, %HTTPoison.Response{body: body, status_code: code}} ->
        IO.puts("HTTP Error #{code}: #{body}")

      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts("Request Error: #{inspect(reason)}")
    end
  end
end

FonbnkClient.main()
```

{% endtab %}
{% endtabs %}


# KYC flow

Most flows require validating a user’s Know Your Customer (KYC) level before creating an order. The high-level steps are:\ <br>

1. Call [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state) with a user's email and a country.
2. If <mark style="color:yellow;">`requiredKycType`</mark> is non-null and higher than <mark style="color:yellow;">`passedKycType`</mark>, the user has already crossed an aggregate threshold and must upgrade KYC before further orders. Skip to step 5.
3. Otherwise, inspect <mark style="color:yellow;">`kycSettings`</mark> to determine whether the order you're about to create triggers a per-order rule:
   * Match settings where <mark style="color:yellow;">`operationType`</mark> and <mark style="color:yellow;">`currencyType`</mark> align with the order side you care about.
   * For per-order entries (<mark style="color:yellow;">`min`</mark>/<mark style="color:yellow;">`max`</mark> set), check whether the order's USD value falls in <mark style="color:yellow;">`[min, max)`</mark>.
   * Take the highest <mark style="color:yellow;">`type`</mark> among all matching entries.
4. If <mark style="color:yellow;">`passedKycType`</mark> is already at or above the required tier, proceed to quoting and order creation.
5. Otherwise, pick the document from <mark style="color:yellow;">`kycDocuments`</mark> whose <mark style="color:yellow;">`type`</mark> matches the required tier, collect <mark style="color:yellow;">`requiredFields`</mark> from the user, and submit via **Submit user KYC**. The response is the same shape as Get user KYC state.
6. Poll **Get user KYC state** until <mark style="color:yellow;">`currentKycStatus === "approved"`</mark> and <mark style="color:yellow;">`passedKycType`</mark> reaches the required tier. (For Nigerian advanced KYC, <mark style="color:yellow;">`currentKycPhase`</mark> transitions from <mark style="color:yellow;">`bvn_check`</mark> to <mark style="color:yellow;">`images_check`</mark> internally — no extra action needed.)

\
**Note on amount-based pre-checks:** <mark style="color:yellow;">`requiredKycType`</mark> reflects only the user's lifetime *successful* order history (aggregate rules). It does not account for the size of the next order. To pre-check whether a specific upcoming order will trip a per-order rule, evaluate <mark style="color:yellow;">`kycSettings`</mark> yourself as described in step 3.

**Tip:** In the sample below, a per-order rule requires <mark style="color:yellow;">`advanced`</mark> KYC for any single crypto payout of $100 or more, and an aggregate rule requires <mark style="color:yellow;">`advanced`</mark> once the user's lifetime successful crypto payouts exceed $1,000.

### Response fields

* **`passedKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | null`</mark>) — the highest KYC tier the user has already passed.
* **`reachedKycLimit`** (<mark style="color:yellow;">`boolean`</mark>) — <mark style="color:yellow;">`true`</mark> when the user has too many pending KYC submissions and cannot initiate another until existing ones resolve.
* **`requiredKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | null`</mark>) — server-computed required tier based on the user's lifetime successful order history (aggregate rules only). If non-null and higher than <mark style="color:yellow;">`passedKycType`</mark>, the user must upgrade KYC. <mark style="color:yellow;">`null`</mark> means no aggregate rule is forcing an upgrade — a per-order rule in <mark style="color:yellow;">`kycSettings`</mark> may still apply to the next order.
* **`currentKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | undefined`</mark>) — the tier of the user's latest in-flight KYC submission.
* **`currentKycStatus`** (<mark style="color:yellow;">`"initiated" | "approved" | "rejected" | "invalid" | null`</mark>) — status of the latest KYC submission.
* **`currentKycStatusDescription`** (<mark style="color:yellow;">`string | null`</mark>) — human-readable reason for the current status.
* **`currentKycPhase`** (<mark style="color:yellow;">`"bvn_check" | "images_check" | undefined`</mark>) — set only for Nigerian advanced KYC. Internal sub-step of an advanced verification. Merchants submit all fields (including images) in a single call; phases progress internally. Use only for progress UI.
* **`kycDocuments`** — list of documents available for the country, each with <mark style="color:yellow;">`_id`</mark>, <mark style="color:yellow;">`type`</mark>, <mark style="color:yellow;">`title`</mark>, <mark style="color:yellow;">`value`</mark>, and a <mark style="color:yellow;">`requiredFields`</mark> array.
* **`kycSettings`** — list of KYC rules for the country. Two variants coexist:
  * **Per-order** — <mark style="color:yellow;">`min`</mark> and <mark style="color:yellow;">`max`</mark> are set. The rule fires when the order's USD value on the matching <mark style="color:yellow;">`operationType`</mark> + <mark style="color:yellow;">`currencyType`</mark> side is in <mark style="color:yellow;">`[min, max)`</mark>. <mark style="color:yellow;">`max`</mark> may be <mark style="color:yellow;">`"Infinity"`</mark>.
  * **Aggregate** — <mark style="color:yellow;">`maxOrdersCount`</mark> and/or <mark style="color:yellow;">`maxAmountUsd`</mark> are set. The rule fires once the user's lifetime successful order count or USD volume in that <mark style="color:yellow;">`(operationType, currencyType)`</mark> bucket reaches the threshold. This is what `requiredKycType` reflects.
  * All settings carry <mark style="color:yellow;">`operationType`</mark>, <mark style="color:yellow;">`currencyType`</mark>, and <mark style="color:yellow;">`type`</mark> (required KYC tier). When multiple fire, the highest <mark style="color:yellow;">`type`</mark> wins.

Sample [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state) response

{% code overflow="wrap" expandable="true" %}

```json
{
  "passedKycType": null,
  "reachedKycLimit": false,
  "currentKycType": null,
  "currentKycStatus": null,
  "currentKycStatusDescription": null,
  "currentKycPhase": null,
  "requiredKycType": null,
  "kycDocuments": [
    {
      "_id": "67da909b739fc481aa525c43",
      "type": "basic",
      "title": "Voter ID",
      "value": "VOTER_ID",
      "requiredFields": [
        { "key": "first_name", "type": "string", "label": "First Name", "required": true },
        { "key": "last_name",  "type": "string", "label": "Last Name",  "required": true },
        { "key": "dob",        "type": "date",   "label": "Date of birth", "required": true },
        {
          "key": "id_number",
          "type": "string",
          "label": "ID number",
          "required": true,
          "format": "0000000000000000000",
          "regexp": "^[a-zA-Z0-9 ]{9,29}$",
          "regexpFlags": "i"
        }
      ]
    },
    {
      "_id": "67da93c0dfd3a00f3380b857",
      "type": "advanced",
      "title": "Driving License",
      "value": "DRIVERS_LICENSE",
      "requiredFields": [
        { "key": "first_name", "type": "string", "label": "First Name", "required": true },
        { "key": "last_name",  "type": "string", "label": "Last Name",  "required": true },
        { "key": "dob",        "type": "date",   "label": "Date of birth", "required": true },
        { "key": "images",     "type": "smile-identity-images", "label": "Verification images", "required": true }
      ]
    }
  ],
  "kycSettings": [
    { "operationType": "deposit", "currencyType": "crypto", "type": "basic",    "min": 0,   "max": 100 },
    { "operationType": "payout",  "currencyType": "crypto", "type": "advanced", "min": 100, "max": "Infinity" },
    { "operationType": "payout",  "currencyType": "crypto", "type": "advanced", "maxAmountUsd": 1000 }
  ]
}
```

{% endcode %}

The "advanced" KYC document requires images. Submit them as an array of objects with image\_type\_id and image, where image is a base64-encoded string. Images must be sent as base64 strings only — file uploads and image URLs are no longer accepted. The combined size of the request body and images must be less than 10MB. The "image\_type\_id" can accept the following values:

* 2: Selfie (base64-encoded)
* 3: Document front (base64-encoded)
* 7: Document back (base64-encoded)

Sample <mark style="color:yellow;">Submit user KYC</mark> request

```json
{
  "userEmail": "someuser@example.com",
  "countryIsoCode": "NG",
  "documentId": "67da93c0dfd3a00f3380b857",
  "userFields": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-01-01",
    "images": [
      { "image_type_id": 2, "image": "<base64-encoded-selfie>" },
      { "image_type_id": 3, "image": "<base64-encoded-document-front>" },
      { "image_type_id": 7, "image": "<base64-encoded-document-back>" }
    ]
  }
}
```

The Submit response has the same shape as [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state). Continue polling [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state) until <mark style="color:yellow;">`currentKycStatus`</mark> becomes <mark style="color:yellow;">`approved`</mark> and <mark style="color:yellow;">`passedKycType`</mark> reaches the required tier, then proceed with quoting and order creation.


# Order statuses

{% @mermaid/diagram content="flowchart TD
%% Pastel Styles
classDef expectedFlow fill:#B5E3FF,stroke:#4A90E2,stroke-width:2px,color:#000,rx:8px,ry:8px
classDef payout fill:#C5F7C1,stroke:#4CAF50,stroke-width:2px,color:#000,rx:8px,ry:8px
classDef refund fill:#FFE5B4,stroke:#FF9800,stroke-width:2px,color:#000,rx:8px,ry:8px
classDef fail fill:#F8C8DC,stroke:#E57373,stroke-width:2px,color:#000,rx:8px,ry:8px
classDef terminal fill:#E0E0E0,stroke:#9E9E9E,stroke-width:2px,color:#000,rx:8px,ry:8px

```
%% Deposit states
A[deposit_awaiting]:::expectedFlow --> B[deposit_validating]:::expectedFlow
A --> E2[deposit_canceled]:::terminal
A --> D2[deposit_invalid]:::terminal
A --> F2[deposit_expired]:::terminal
B --> C[deposit_successful]:::expectedFlow
B --> D[deposit_invalid]:::terminal
B --> E[deposit_canceled]:::terminal
B --> F[deposit_expired]:::terminal

%% Payout states
C --> G[payout_pending]:::expectedFlow
G --> H[payout_successful]:::payout
G --> J[payout_failed]:::fail
J --> H


%% Refund states
J --> K[refund_pending]:::refund
K --> L[refund_successful]:::refund
K --> M[refund_failed]:::fail
M --> |Retry until refund_successful| L" %}
```

Statuses explanation:

* deposit\_awaiting – waiting for a user to make the deposit
* deposit\_validating – user confirmed deposit, validating
* deposit\_invalid – deposit was invalid (e.g., wrong amount, wrong reference)
* deposit\_successful – deposit validated successfully, initiating payout
* deposit\_canceled – order was canceled by a user before deposit was made
* deposit\_expired – deposit was not made in time
* payout\_pending – payout is being processed
* payout\_successful – payout completed successfully
* payout\_failed – payout failed (e.g., invalid wallet address, blockchain failure)
* refund\_pending – refund is being processed
* refund\_successful – refund completed successfully
* refund\_failed – refund failed (e.g., bank rejection, blockchain failure)


# Webhooks

There are 2 ways to get notified of order status changes via webhooks:

1. Global webhook URL set in the merchant dashboard (applies to all orders)
2. Per-order webhookUrl field when creating an order (overrides global URL for that order)

{% hint style="info" %}
You can read how to set a global webhook endpoint [HERE](/server-to-server/getting-started)
{% endhint %}

When an order status changes, a POST request is sent to the webhook URL with the following payload type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Webhook = {
  event: 'order-status-change';
  data: {
    order: {
      userId: string;
      userEmail: string;
      merchantOrderParams?: string;
      countryIsoCode: string;
      flow: FlowType;
      type: OrderType;
      source: Source;
      status: OrderStatus;
      deposit: {
        paymentChannel: PaymentChannel;
        currencyType: CurrencyType;
        currencyCode: string;
        cashout: {
          exchangeRate: number;
          exchangeRateAfterFees: number;
          amountBeforeFees: number;
          amountAfterFees: number;
          amountBeforeFeesUsd: number;
          amountAfterFeesUsd: number;
        };
      };
      payout: {
        paymentChannel: PaymentChannel;
        currencyType: CurrencyType;
        currencyCode: string;
        cashout: {
          exchangeRate: number;
          exchangeRateAfterFees: number;
          amountBeforeFees: number;
          amountAfterFees: number;
          amountBeforeFeesUsd: number;
          amountAfterFeesUsd: number;
        };
        transaction?: {
          meta?: {
            transactionHash?: string;
          };
        };
      };
      refund?: {
        paymentChannel: PaymentChannel;
        currencyType: CurrencyType;
        currencyCode: string;
        cashout: {
          exchangeRate: number;
          exchangeRateAfterFees: number;
          amountBeforeFees: number;
          amountAfterFees: number;
          amountBeforeFeesUsd: number;
          amountAfterFeesUsd: number;
        };
        transaction?: {
          meta?: {
            transactionHash?: string;
          };
        };
      };
      createdAt: Date;
      updatedAt: Date;
    };
    userKyc?: {
      passedKycType?: KycType;
      passedKycHash?: string; // unique KYC submission identifier
      latestKycType?: KycType;
      latestKycStatus?: KycStatus;
    };
  };
}
```

{% endcode %}

Example payload

{% code overflow="wrap" expandable="true" %}

```json
{
  "event": "order-status-change",
  "data": {
    "order": {
      "userId": "68df8fcb372f378356ef7568",
      "userEmail": "chauncey69@gmail.com",
      "merchantOrderParams": "01K6MMKBKC8CX4SMJAR49DX5RZ",
      "countryIsoCode": "NG",
      "flow": "regular",
      "type": "on_ramp",
      "source": "api",
      "status": "payout_successful",
      "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "cashout": {
          "exchangeRate": 1460.2,
          "exchangeRateAfterFees": 1505.2969,
          "amountBeforeFees": 15054,
          "amountAfterFees": 14603,
          "amountBeforeFeesUsd": 10.309547,
          "amountAfterFeesUsd": 10.000685
        }
      },
      "payout": {
        "paymentChannel": "merchant_balance",
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "cashout": {
          "exchangeRate": 1,
          "exchangeRateAfterFees": 1,
          "amountBeforeFees": 10,
          "amountAfterFees": 10,
          "amountBeforeFeesUsd": 10,
          "amountAfterFeesUsd": 10
        }
      },
      "createdAt": "2025-10-03T08:56:43.212Z",
      "updatedAt": "2025-10-03T08:57:03.247Z"
    }
  }
}
```

{% endcode %}

#### Webhook Verification

We send a signature with each webhook request to protect merchants from fraudulent requests. Each request should be verified using a secret provided in the merchant dashboard.

**The signature is sent in the&#x20;**<mark style="color:$warning;">**x-signature**</mark>**&#x20;HTTP header.**

The signature is computed as follows:

```pseudocode
x-signature === SHA256(SHA256(secret) + JSON.stringify(request.body))
```

**TypeScript example:**

```typescript
import { createHash } from 'crypto';

function verifyWebhookSignature(
  requestBody: any,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = createHash('sha256')
    .update(JSON.stringify(requestBody))
    .update(createHash('sha256').update(secret, 'utf8').digest('hex'))
    .digest('hex');
  
  return signature === expectedSignature;
}
```

#### Webhook Response Requirements

Your webhook endpoint must:

* Respond with HTTP status code **2xx** to acknowledge receipt
* Respond within **20 seconds** (requests taking longer will timeout)

Any other status code or timeout will be considered a failure.

#### Retry Policy

If your webhook endpoint fails to respond successfully:

* We will retry up to **10 times**
* Retries will be attempted with exponential backoff: 1sec, 2sec, 4sec, 8sec, 16sec, 32sec, 64sec, 128sec, 256sec, 512sec
* Webhooks can be viewed in the merchant dashboard


# KYC and auth webhooks

By default you'll receive only webhooks related to order status change. But you can opt-in for additional webhooks:

* auth - you will receive the "auth" event on each login/register initiation/success/failure
* KYC - you will receive the "kyc" event on a user KYC submission status change

These events can be enabled at the Webhooks -> Settings page of the merchant dasboard.

Type of the auth events:

```ts
type AuthWebhook = {
  "event": "auth",
  "data": {
    "authOperation": "login" | "register",
    "authStatus": "initiate" | "success" | "failed",
    "userEmail": string,
    "date": string,
    "userCountryIsoCode": string,
    "userId"?: string
  }
}
```

Type of the KYC events:

```typescript
type KycWebhook = {
  "event": "kyc",
  "data": {
    "kycId": string,
    "kycStatus": "initiated" | "approved" | "rejected",
    "kycType": "basic" | "advanced",
    "userId": string,
    "userEmail": string,
    "userCountryIsoCode": string,
    "kycDocument": string,
    "date": string
  }
}
```


# API Endpoints


# Get available currencies

## <mark style="color:$success;">\[GET]</mark> /api/v2/currencies

Returns supported currencies for deposit and payout with details and available pairs.

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  currencyType: CurrencyType;
  currencyCode: string;
  paymentChannels: {
    type: PaymentChannel,
    transferTypes: TransferType[],
    isDepositAllowed: boolean,
    isPayoutAllowed: boolean,
    carriers?: { code: string; name: string; }[]
  }[];
  currencyDetails: OrderCurrencyDetails;
  pairs: CurrencyType[]; // available counter currency types
}[]
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
[
    {
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "paymentChannels": [
            {
                "type": "bank",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            },
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ng_glo",
                        "name": "Glo Mobile Nigeria"
                    },
                    {
                        "code": "ng_9mobile",
                        "name": "9Mobile Nigeria"
                    },
                    {
                        "code": "ng_airtel",
                        "name": "Airtel Nigeria"
                    },
                    {
                        "code": "ng_mtn",
                        "name": "MTN Nigeria"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ng_glo",
                        "name": "Glo Mobile Nigeria"
                    },
                    {
                        "code": "ng_9mobile",
                        "name": "9Mobile Nigeria"
                    },
                    {
                        "code": "ng_airtel",
                        "name": "Airtel Nigeria"
                    },
                    {
                        "code": "ng_mtn",
                        "name": "MTN Nigeria"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "KES",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "stk_push",
                    "otp_stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ke_safaricom",
                        "name": "Safaricom Kenya"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ke_safaricom",
                        "name": "Safaricom Kenya"
                    },
                    {
                        "code": "ke_telkom",
                        "name": "Telkom Kenya"
                    },
                    {
                        "code": "ke_airtel",
                        "name": "Airtel Kenya"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "KE"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "GHS",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "gh_vodafone",
                        "name": "Vodafone Ghana"
                    },
                    {
                        "code": "gh_mtn",
                        "name": "MTN Ghana"
                    },
                    {
                        "code": "gh_airtel_tigo",
                        "name": "AirtelTigo Ghana"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "gh_vodafone",
                        "name": "Vodafone Ghana"
                    },
                    {
                        "code": "gh_mtn",
                        "name": "MTN Ghana"
                    },
                    {
                        "code": "gh_airtel_tigo",
                        "name": "AirtelTigo Ghana"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "GH"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "ZAR",
        "paymentChannels": [
            {
                "type": "bank",
                "transferTypes": [
                    "manual",
                    "redirect"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            },
            {
                "type": "airtime",
                "transferTypes": [],
                "isDepositAllowed": false,
                "isPayoutAllowed": false,
                "carriers": [
                    {
                        "code": "za_vodacom",
                        "name": "Vodacom South Africa"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "ZA"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "TZS",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "tz_halotel",
                        "name": "Halotel Tanzania"
                    },
                    {
                        "code": "tz_airtel",
                        "name": "Airtel Tanzania"
                    },
                    {
                        "code": "tz_tigo",
                        "name": "Tigo Tanzania"
                    },
                    {
                        "code": "tz_vodacom",
                        "name": "Vodacom Tanzania"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "tz_halotel",
                        "name": "Halotel Tanzania"
                    },
                    {
                        "code": "tz_airtel",
                        "name": "Airtel Tanzania"
                    },
                    {
                        "code": "tz_tigo",
                        "name": "Tigo Tanzania"
                    },
                    {
                        "code": "tz_vodacom",
                        "name": "Vodacom Tanzania"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "TZ"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "UGX",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ug_mtn",
                        "name": "MTN Uganda"
                    },
                    {
                        "code": "ug_airtel",
                        "name": "Airtel Uganda"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "ug_mtn",
                        "name": "MTN Uganda"
                    },
                    {
                        "code": "ug_airtel",
                        "name": "Airtel Uganda"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "UG"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "MWK",
        "paymentChannels": [
            {
                "type": "airtime",
                "transferTypes": [],
                "isDepositAllowed": false,
                "isPayoutAllowed": false,
                "carriers": [
                    {
                        "code": "mw_airtel",
                        "name": "Airtel Malawi"
                    },
                    {
                        "code": "mw_tnm",
                        "name": "TNM Malawi"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "MW"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "XOF",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "bf_onatel",
                        "name": "Onatel Burkina Faso"
                    },
                    {
                        "code": "bf_orange",
                        "name": "Orange Burkina Faso"
                    },
                    {
                        "code": "sn_free",
                        "name": "Free Senegal"
                    },
                    {
                        "code": "sn_orange",
                        "name": "Orange Senegal"
                    },
                    {
                        "code": "bj_moov",
                        "name": "Moov Benin"
                    },
                    {
                        "code": "bj_mtn",
                        "name": "BJ MTN Benin"
                    },
                    {
                        "code": "ci_mtn",
                        "name": "MTN Ivory Coast"
                    },
                    {
                        "code": "ci_orange",
                        "name": "Orange Ivory Coast"
                    },
                    {
                        "code": "cg_airtel",
                        "name": "CG Bharti Airtel"
                    },
                    {
                        "code": "cg_mtn",
                        "name": "MTN Republic of the Congo"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "bf_onatel",
                        "name": "Onatel Burkina Faso"
                    },
                    {
                        "code": "bf_orange",
                        "name": "Orange Burkina Faso"
                    },
                    {
                        "code": "bj_mtn",
                        "name": "BJ MTN Benin"
                    },
                    {
                        "code": "bj_moov",
                        "name": "Moov Benin"
                    },
                    {
                        "code": "cg_airtel",
                        "name": "CG Bharti Airtel"
                    },
                    {
                        "code": "cg_mtn",
                        "name": "MTN Republic of the Congo"
                    },
                    {
                        "code": "ci_mtn",
                        "name": "MTN Ivory Coast"
                    },
                    {
                        "code": "ci_orange",
                        "name": "Orange Ivory Coast"
                    },
                    {
                        "code": "sn_free",
                        "name": "Free Senegal"
                    },
                    {
                        "code": "sn_orange",
                        "name": "Orange Senegal"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "BF"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "BRL",
        "paymentChannels": [
            {
                "type": "bank",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "BR"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "ZMW",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "zm_mtn",
                        "name": "MTN Zambia"
                    },
                    {
                        "code": "zm_airtel",
                        "name": "Airtel Zambia"
                    },
                    {
                        "code": "zm_zamtel",
                        "name": "Zamtel Zambia"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "zm_mtn",
                        "name": "MTN Zambia"
                    },
                    {
                        "code": "zm_airtel",
                        "name": "Airtel Zambia"
                    },
                    {
                        "code": "zm_zamtel",
                        "name": "Zamtel Zambia"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "ZM"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "RWF",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "rw_airtel",
                        "name": "Airtel Rwanda"
                    },
                    {
                        "code": "rw_mtn",
                        "name": "MTN Rwanda"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "rw_airtel",
                        "name": "Airtel Rwanda"
                    },
                    {
                        "code": "rw_mtn",
                        "name": "MTN Rwanda"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "RW"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "fiat",
        "currencyCode": "XAF",
        "paymentChannels": [
            {
                "type": "mobile_money",
                "transferTypes": [
                    "otp_stk_push",
                    "stk_push"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "cm_orange",
                        "name": "Orange Cameroon"
                    },
                    {
                        "code": "cm_mtn",
                        "name": "MTN Cameroon"
                    },
                    {
                        "code": "gb_airtel",
                        "name": "Airtel Gabon"
                    },
                    {
                        "code": "cg_airtel",
                        "name": "CG Bharti Airtel"
                    },
                    {
                        "code": "cg_mtn",
                        "name": "MTN Republic of the Congo"
                    }
                ]
            },
            {
                "type": "airtime",
                "transferTypes": [
                    "ussd"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true,
                "carriers": [
                    {
                        "code": "cm_orange",
                        "name": "Orange Cameroon"
                    },
                    {
                        "code": "cm_mtn",
                        "name": "MTN Cameroon"
                    },
                    {
                        "code": "gb_airtel",
                        "name": "Airtel Gabon"
                    }
                ]
            }
        ],
        "currencyDetails": {
            "countryIsoCode": "CM"
        },
        "pairs": [
            "crypto",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "merchant_balance",
        "currencyCode": "USD",
        "paymentChannels": [
            {
                "type": "merchant_balance",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "merchantName": "Fonbnk"
        },
        "pairs": [
            "fiat",
            "crypto"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "SOLANA_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": false,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "SOLANA",
            "asset": "USDT",
            "contractAddress": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "TON_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": false
            }
        ],
        "currencyDetails": {
            "network": "TON",
            "asset": "USDT",
            "contractAddress": "EQDbg2Pq90CYsmQunxbz3_LJSRTPegqiq4KPFnYoe-IqusAK"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "TON_USDE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": false
            }
        ],
        "currencyDetails": {
            "network": "TON",
            "asset": "USDE",
            "contractAddress": "EQDbg2Pq90CYsmQunxbz3_LJSRTPegqiq4KPFnYoe-IqusAK"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "LISK_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "LISK",
            "asset": "USDT",
            "contractAddress": "0xee07a6d5574b1d24a4d9ca98ccf78dcbff8792cb"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ARBITRUM_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ARBITRUM",
            "asset": "USDT",
            "contractAddress": "0x281bb260a3c17f08742c762d73c4263f95574b4e"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "AVALANCHE_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "AVALANCHE",
            "asset": "USDC",
            "contractAddress": "0x8f59f57d59d6412b735072ee051f17f8ff85bb56"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "AVALANCHE_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "AVALANCHE",
            "asset": "USDT",
            "contractAddress": "0x8f59f57d59d6412b735072ee051f17f8ff85bb56"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "BASE_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "BASE",
            "asset": "USDC",
            "contractAddress": "0x52be646529e3ceebba8dc800f67c64a110799045"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_CUSD",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "CUSD",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "NATIVE",
            "contractAddress": null
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "USDC",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_USDGLO",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": false,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "USDGLO",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "USDT",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_CKES",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "CKES",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "CELO_CGHS",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "CELO",
            "asset": "CGHS",
            "contractAddress": "0xd5cf8ab08295e32db2e2dce8478e5ce021340a57"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ETHEREUM_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ETHEREUM",
            "asset": "USDC",
            "contractAddress": "0x251ae8ffa50fe1e586e4385242caeef8ed222e5b"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ETHEREUM_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ETHEREUM",
            "asset": "USDT",
            "contractAddress": "0x251ae8ffa50fe1e586e4385242caeef8ed222e5b"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ETHEREUM_RLUSD",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ETHEREUM",
            "asset": "RLUSD",
            "contractAddress": "0x251ae8ffa50fe1e586e4385242caeef8ed222e5b"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDC",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDC_E",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": false,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDC_E",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDGLO",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": false,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDGLO",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "SOLANA_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "SOLANA",
            "asset": "USDC",
            "contractAddress": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ARBITRUM_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ARBITRUM",
            "asset": "USDC",
            "contractAddress": "0x281bb260a3c17f08742c762d73c4263f95574b4e"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "OPTIMISM_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "OPTIMISM",
            "asset": "USDC",
            "contractAddress": "0x115473c4ad9c0f8f87f443a843b3c4c3a04d7760"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "OPTIMISM_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "OPTIMISM",
            "asset": "USDT",
            "contractAddress": "0x115473c4ad9c0f8f87f443a843b3c4c3a04d7760"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "BNB_USDC",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "BNB",
            "asset": "USDC",
            "contractAddress": "0x51be56f99e091920f6894356377ebf589dd18682"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "BNB_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "BNB",
            "asset": "USDT",
            "contractAddress": "0x51be56f99e091920f6894356377ebf589dd18682"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "TRON_USDT",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "TRON",
            "asset": "USDT",
            "contractAddress": "TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "XRP_RLUSD",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "XRP",
            "asset": "RLUSD",
            "contractAddress": "524C555344000000000000000000000000000000"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "ETHEREUM_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "ETHEREUM",
            "asset": "NATIVE",
            "contractAddress": null
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "SOLANA_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "SOLANA",
            "asset": "NATIVE",
            "contractAddress": null
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "TON_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "TON",
            "asset": "NATIVE",
            "contractAddress": ""
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "TRON_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "TRON",
            "asset": "NATIVE",
            "contractAddress": ""
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    },
    {
        "currencyType": "crypto",
        "currencyCode": "XRP_NATIVE",
        "paymentChannels": [
            {
                "type": "crypto",
                "transferTypes": [
                    "manual"
                ],
                "isDepositAllowed": true,
                "isPayoutAllowed": true
            }
        ],
        "currencyDetails": {
            "network": "XRP",
            "asset": "NATIVE",
            "contractAddress": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
        },
        "pairs": [
            "fiat",
            "merchant_balance"
        ]
    }
]
```

{% endcode %}


# Get order limits

## <mark style="color:$success;">\[GET]</mark> /api/v2/order-limits

Returns min and max order amount limits for a deposit and payout currency pair.

Request query params type:

```typescript
type QueryParams = {
    depositPaymentChannel: PaymentChannel,// required
    depositCurrencyType: CurrencyType,// required
    depositCurrencyCode: string,// required
    depositCarrierCode?: string,// optional
    depositCountryIsoCode?: string,// required if depositCurrencyType is fiat
    payoutPaymentChannel: PaymentChannel,// required,
    payoutCurrencyType: CurrencyType,// required
    payoutCurrencyCode: string,// required
    payoutCarrierCode?: string,// optional
    payoutCountryIsoCode?: string// required if payoutCurrencyType is fiat
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/order-limits?depositPaymentChannel=bank&depositCurrencyType=fiat&depositCurrencyCode=NGN&depositCountryIsoCode=NG&payoutPaymentChannel=crypto&payoutCurrencyType=crypto&payoutCurrencyCode=CELO_USDT
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  deposit: { min: number; max: number; minUsd: number; maxUsd: number },
  payout: { min: number; max: number; minUsd: number; maxUsd: number },
}
```

{% endcode %}

Response example:

```json
{
    "deposit": {
        "min": 1494,
        "max": 747367,
        "minUsd": 1,
        "maxUsd": 500
    },
    "payout": {
        "min": 1,
        "max": 500,
        "minUsd": 1,
        "maxUsd": 500
    }
}
```


# Create quote

## <mark style="color:$warning;">\[POST]</mark> /api/v2/quote

Generates a qoute that can be used for an order creation

Request body type:

```typescript
type RequestBody = {
  deposit: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    countryIsoCode?: string;
    amount?: number;
  };
  payout: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    countryIsoCode?: string;
    amount?: number;
  };
};
```

Request body example:

```json
{
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG",
        "amount": 10000
    },
    "payout": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT"
    }
}
```

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  quoteId: string;
  quoteExpiresAt: Date;
  deposit: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    fieldsToCreateOrder: RequiredField[];
    transferType: TransferType;
  },
  payout: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    fieldsToCreateOrder: RequiredField[];
  }
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "6928130ca263ba8d44fad2cf",
    "quoteExpiresAt": "2025-11-27T09:29:56.167Z",
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "cashout": {
            "amountBeforeFees": 10000,
            "amountAfterFees": 9650,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 250
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 100
                }
            ],
            "totalChargedFees": 350,
            "chargedFeesPerRecipient": {
                "platform": 250,
                "merchant": 100
            },
            "amountBeforeFeesUsd": 6.821794,
            "amountAfterFeesUsd": 6.583031,
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 0.170545
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 0.068218
                }
            ],
            "totalChargedFeesUsd": 0.238763,
            "exchangeRate": 1465.89,
            "exchangeRateAfterFees": 1519.0571,
            "chargedFeesPerRecipientUsd": {
                "platform": 0.170545,
                "merchant": 0.068218
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    },
                    {
                        "label": "Deposit underpayment (50%)",
                        "value": "deposit_underpayment"
                    },
                    {
                        "label": "Deposit overpayment (200%)",
                        "value": "deposit_overpayment"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "cashout": {
            "amountBeforeFees": 6.583031,
            "amountAfterFees": 6.582386,
            "chargedFees": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "totalChargedFees": 0.000645,
            "chargedFeesPerRecipient": {
                "blockchain": 0.000645
            },
            "amountBeforeFeesUsd": 6.583031,
            "amountAfterFeesUsd": 6.582386,
            "chargedFeesUsd": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "totalChargedFeesUsd": 0.000645,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1.0001,
            "chargedFeesPerRecipientUsd": {
                "blockchain": 0.000645
            },
            "feeSettings": [
                {
                    "id": "gas",
                    "recipient": "blockchain",
                    "type": "flat_amount",
                    "value": 0.000645,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "blockchainWalletAddress",
                "type": "string",
                "label": "Your wallet address",
                "required": true
            },
            {
                "key": "blockchainMemo",
                "type": "string",
                "label": "Memo",
                "required": false
            },
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "required": false,
                "defaultValue": "payout_success",
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed",
                        "value": "payout_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}


# Create order

## <mark style="color:$warning;">\[POST]</mark> /api/v2/order

Creates an order. If a quote is provided, it uses the pricing from it.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  quoteId?: string;
  userCountryIsoCode: string; 
  userEmail: string;
  userIp: string;
  deposit: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    carrierCode?: string;
    countryIsoCode?: string; // required if currencyType is fiat
    amount?: number;
  },
  payout: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    carrierCode?: string;
    countryIsoCode?: string; // required if currencyType is fiat
    amount?: number;
  };
  fieldsToCreateOrder: Record<string, any>; // union of required fields from deposit and payout
  orderParams?: string; // merchant-defined reference
  callbackUrl?: string; // button URL on status page in the widget
  webhookUrl?: string; // optional webhook URL for this specific order status updates, it will override the default one set for the merchant
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
    "userCountryIsoCode": "NG",
    "userEmail": "testuser+ng@fonbnk.com",
    "userIp": "223.134.123.12",
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "countryIsoCode": "NG"
    },
    "payout": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "amount": 1
    },
    "fieldsToCreateOrder": {
        "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
        "phoneNumber": "2348012345678",
        "bankCode": "1",
        "bankAccountNumber": "1234567890"
    }
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  quoteId: string;
  quoteExpiresAt: Date;
  deposit: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    fieldsToCreateOrder: RequiredField[];
    transferType: TransferType;
  },
  payout: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    fieldsToCreateOrder: RequiredField[];
  }
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
    "quoteId": "6928130ca263ba8d44fad2cf",
    "quoteExpiresAt": "2025-11-27T09:29:56.167Z",
    "deposit": {
        "paymentChannel": "bank",
        "currencyType": "fiat",
        "currencyCode": "NGN",
        "currencyDetails": {
            "countryIsoCode": "NG"
        },
        "cashout": {
            "amountBeforeFees": 10000,
            "amountAfterFees": 9650,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 250
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 100
                }
            ],
            "totalChargedFees": 350,
            "chargedFeesPerRecipient": {
                "platform": 250,
                "merchant": 100
            },
            "amountBeforeFeesUsd": 6.821794,
            "amountAfterFeesUsd": 6.583031,
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 0.170545
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 0.068218
                }
            ],
            "totalChargedFeesUsd": 0.238763,
            "exchangeRate": 1465.89,
            "exchangeRateAfterFees": 1519.0571,
            "chargedFeesPerRecipientUsd": {
                "platform": 0.170545,
                "merchant": 0.068218
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "phoneNumber",
                "label": "Phone Number",
                "required": true,
                "type": "phone"
            },
            {
                "key": "bankCode",
                "label": "Bank name",
                "required": true,
                "type": "enum",
                "options": [
                    {
                        "label": "Sandbox Bank",
                        "value": "1"
                    },
                    {
                        "label": "Sandbox Bank 2",
                        "value": "2"
                    },
                    {
                        "label": "Sandbox Bank 3",
                        "value": "3"
                    }
                ]
            },
            {
                "key": "bankAccountNumber",
                "label": "Bank Account Number",
                "required": true,
                "type": "string"
            },
            {
                "key": "depositSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox deposit forced flow",
                "required": false,
                "defaultValue": "deposit_success",
                "options": [
                    {
                        "label": "Deposit success",
                        "value": "deposit_success"
                    },
                    {
                        "label": "Deposit invalid",
                        "value": "deposit_invalid"
                    },
                    {
                        "label": "Deposit underpayment (50%)",
                        "value": "deposit_underpayment"
                    },
                    {
                        "label": "Deposit overpayment (200%)",
                        "value": "deposit_overpayment"
                    }
                ]
            }
        ],
        "transferType": "manual"
    },
    "payout": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "cashout": {
            "amountBeforeFees": 6.583031,
            "amountAfterFees": 6.582386,
            "chargedFees": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "totalChargedFees": 0.000645,
            "chargedFeesPerRecipient": {
                "blockchain": 0.000645
            },
            "amountBeforeFeesUsd": 6.583031,
            "amountAfterFeesUsd": 6.582386,
            "chargedFeesUsd": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "totalChargedFeesUsd": 0.000645,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1.0001,
            "chargedFeesPerRecipientUsd": {
                "blockchain": 0.000645
            },
            "feeSettings": [
                {
                    "id": "gas",
                    "recipient": "blockchain",
                    "type": "flat_amount",
                    "value": 0.000645,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "fieldsToCreateOrder": [
            {
                "key": "blockchainWalletAddress",
                "type": "string",
                "label": "Your wallet address",
                "required": true
            },
            {
                "key": "blockchainMemo",
                "type": "string",
                "label": "Memo",
                "required": false
            },
            {
                "key": "payoutSandboxForcedFlow",
                "type": "enum",
                "label": "Sandbox payout forced flow",
                "required": false,
                "defaultValue": "payout_success",
                "options": [
                    {
                        "label": "Payout success",
                        "value": "payout_success"
                    },
                    {
                        "label": "Payout failed",
                        "value": "payout_failed"
                    }
                ]
            }
        ]
    }
}
```

{% endcode %}


# Trigger order intermediate action

## <mark style="color:$warning;">\[POST]</mark> /api/v2/order/intermediate-action

Triggers an intermediate action for a deposit order (e.g., STK Push or OTP STK Push). Must be called within the timeout and before max attempts are reached.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;
  fieldsForIntermediateAction: Record<string, string>;
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "68728fa56ff494df5f39faf5",
  "fieldsForIntermediateAction": {
    "otpCode": "123456"
  }
}
```

{% endcode %}

Returns the updated order.


# Confirm order

## <mark style="color:$warning;">\[POST]</mark> /api/v2/order/confirm

Confirms an order. If transferInstructions.fieldsToConfirmOrder of deposit or payout is non-empty, include them.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;
  fieldsToConfirmOrder?: Record<string, string>;
}
```

{% endcode %}

Request body example:

```json
{
  "orderId": "68728fa56ff494df5f39faf5"
}
```

Returns an updated order.


# Cancel order

## <mark style="color:$warning;">\[POST]</mark> /api/v2/order/cancel

Cancels an order if it's still in a cancellable state.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "68728fa56ff494df5f39faf5",
}
```

{% endcode %}

Returns an updated order.


# Get order

## <mark style="color:$success;">\[GET]</mark> /api/v2/order

Gets an order by its ID or merchant order params.

Request query params type:

```typescript
type QueryParams = {
    orderId?: string,// optional
    orderParams?: string,// optional
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/order?orderId=69281d944a1db009177f0198
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  _id: string,
  countryIsoCode: string;
  userId: string;
  userEmail: string;
  merchantOrderParams?: string;
  status: OrderStatus;
  deposit: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    providedFieldsToCreateOrder: Record<string, string>;
    transferInstructions: TransferInstructions;
  },
  payout: {
    paymentChannel: PaymentChannel;
    currencyType: CurrencyType;
    currencyCode: string;
    currencyDetails: OrderCurrencyDetails;
    cashout: Cashout;
    providedFieldsToCreateOrder: Record<string, string>;
    transaction?: {
      meta?: {
        transactionHash?: string;
      }
    }
  },
  createdAt: Date;
  updatedAt: Date;
  expiresAt: Date;
  statusChangeLogs: { oldStatus: OrderStatus; newStatus: OrderStatus; date: Date; }[]
}
```

{% endcode %}

Response example:

{% code overflow="wrap" %}

```json
{
    "_id": "69283d079061f4031ad1ba03",
    "countryIsoCode": "KE",
    "userId": "69283c0518613bc9de730cb4",
    "userEmail": "testuser+ke@fonbnk.com",
    "status": "payout_successful",
    "deposit": {
        "paymentChannel": "mobile_money",
        "currencyType": "fiat",
        "currencyCode": "KES",
        "currencyDetails": {
            "countryIsoCode": "KE",
            "carrier": {
                "code": "ke_safaricom",
                "name": "Safaricom Kenya",
                "_id": "618e43914f57e07d255ff353"
            }
        },
        "cashout": {
            "amountBeforeFees": 135,
            "amountAfterFees": 130,
            "amountBeforeFeesUsd": 1.037823,
            "amountAfterFeesUsd": 0.999385,
            "chargedFees": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 3.38
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 1.35
                }
            ],
            "chargedFeesUsd": [
                {
                    "id": "service_fee",
                    "type": "percentage",
                    "recipient": "platform",
                    "amount": 0.025984
                },
                {
                    "id": "merchant_fee",
                    "type": "percentage",
                    "recipient": "merchant",
                    "amount": 0.010378
                }
            ],
            "totalChargedFees": 4.73,
            "totalChargedFeesUsd": 0.036362,
            "exchangeRate": 130.08,
            "exchangeRateAfterFees": 135.0831,
            "chargedFeesPerRecipient": {
                "platform": 3.38,
                "merchant": 1.35
            },
            "chargedFeesPerRecipientUsd": {
                "platform": 0.025984,
                "merchant": 0.010378
            },
            "feeSettings": [
                {
                    "id": "service_fee",
                    "recipient": "platform",
                    "type": "percentage",
                    "value": 2.5,
                    "min": 0,
                    "max": "Infinity"
                },
                {
                    "id": "merchant_fee",
                    "recipient": "merchant",
                    "type": "percentage",
                    "value": 1,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "providedFieldsToCreateOrder": {
            "phoneNumber": "2348012345678",
            "carrierCode": "ke_safaricom"
        },
        "providedFieldsToConfirmOrder": {},
        "transferInstructions": {
            "type": "otp_stk_push",
            "intermediateActionAttempts": 2,
            "intermediateActionMaxAttempts": 3,
            "intermediateActionButtonText": "Verify OTP code",
            "intermediateActionNextAttemptAvailableAt": "2025-11-27T12:00:14.390Z",
            "intermediateActionTimeoutMs": 30000,
            "isIntermediateActionAvailable": true,
            "forcePhoneNumberVerification": true,
            "fieldsForIntermediateAction": [
                {
                    "key": "otpCode",
                    "label": "OTP code",
                    "type": "number",
                    "required": true
                }
            ],
            "instructionsText": "It is a sandbox offer. Use 123456 as OTP code and confirm the transfer from your side and system will automatically confirm the transfer within 1 minute.",
            "warningText": "Non-confirmed orders will be automatically canceled after 5 minutes.",
            "transferDetails": [
                {
                    "id": "amountToSend",
                    "label": "Amount to send",
                    "value": "135"
                }
            ],
            "fieldsToConfirmOrder": [],
            "intermediateActionRequired": true,
            "intermediateActionExecuted": true,
            "otpChannel": "sms"
        }
    },
    "payout": {
        "paymentChannel": "crypto",
        "currencyType": "crypto",
        "currencyCode": "POLYGON_USDT",
        "currencyDetails": {
            "network": "POLYGON",
            "asset": "USDT",
            "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
        },
        "cashout": {
            "amountBeforeFees": 1.000645,
            "amountAfterFees": 1,
            "amountBeforeFeesUsd": 1.000645,
            "amountAfterFeesUsd": 1,
            "chargedFees": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "chargedFeesUsd": [
                {
                    "id": "gas",
                    "type": "flat_amount",
                    "recipient": "blockchain",
                    "amount": 0.000645
                }
            ],
            "totalChargedFees": 0.000645,
            "totalChargedFeesUsd": 0.000645,
            "exchangeRate": 1,
            "exchangeRateAfterFees": 1.0006,
            "chargedFeesPerRecipient": {
                "blockchain": 0.000645
            },
            "chargedFeesPerRecipientUsd": {
                "blockchain": 0.000645,
                "platform": 0.025984,
                "merchant": 0.010378
            },
            "feeSettings": [
                {
                    "id": "gas",
                    "recipient": "blockchain",
                    "type": "flat_amount",
                    "value": 0.000645,
                    "min": 0,
                    "max": "Infinity"
                }
            ]
        },
        "providedFieldsToCreateOrder": {
            "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
        },
        "transaction": {
            "meta": {
                "transactionHash": "0xe168c39bf7165c0eaa88e4df1e21e987666e44f11f2bea6f9be1c145f382dade"
            }
        }
    },
    "statusChangeLogs": [
        {
            "oldStatus": "deposit_awaiting",
            "newStatus": "deposit_validating",
            "date": "2025-11-27T12:01:43.660Z"
        },
        {
            "oldStatus": "deposit_validating",
            "newStatus": "deposit_successful",
            "date": "2025-11-27T12:02:00.770Z"
        },
        {
            "oldStatus": "deposit_successful",
            "newStatus": "payout_pending",
            "date": "2025-11-27T12:02:01.306Z"
        },
        {
            "oldStatus": "payout_pending",
            "newStatus": "payout_successful",
            "date": "2025-11-27T12:02:19.053Z"
        }
    ],
    "createdAt": "2025-11-27T11:59:03.754Z",
    "updatedAt": "2025-11-27T12:02:19.124Z",
    "expiresAt": "2025-11-27T12:04:03.673Z"
}
```

{% endcode %}


# Get orders

## <mark style="color:$success;">\[GET]</mark> /api/v2/orders

Gets a list of orders with cursor pagination and optional filters.

Request query params type:

```typescript
type QueryParams = {
    limit: number,// required
    cursor?: PaymentChannel,// optional
    userEmail?: string,// optional
    status?: OrderStatus,// optional
    fromDate?: number,// optional, unix mx
    toDate?: number,// optional, unix 
    depositCurrencyCode?: string,// optional
    depositPaymentChannel?: PaymentChannel,// optional
    depositCurrencyType?: CurrencyType,// optional
    payoutCurrencyCode?: string,// optional
    payoutPaymentChannel?: PaymentChannel,// optional
    payoutCurrencyType?: string,// optional
    depositUserWalletAddress?: string,// optional
    payoutUserWalletAddress?: string,// optional
    depositUserPhoneNumber?: string,// optional
    payoutUserPhoneNumber?: string,// optional
}
```

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  nextCursor?: string;
  list: GetOrderResponse[];
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
    "nextCursor": "69283d079061f4031ad1ba03",
    "list": [
        {
            "_id": "69283e024a1db009177f2146",
            "countryIsoCode": "KE",
            "userId": "69283c0518613bc9de730cb4",
            "userEmail": "testuser+ke@fonbnk.com",
            "status": "deposit_canceled",
            "deposit": {
                "paymentChannel": "mobile_money",
                "currencyType": "fiat",
                "currencyCode": "KES",
                "currencyDetails": {
                    "countryIsoCode": "KE",
                    "carrier": {
                        "code": "ke_safaricom",
                        "name": "Safaricom Kenya",
                        "_id": "618e43914f57e07d255ff353"
                    }
                },
                "cashout": {
                    "amountBeforeFees": 135,
                    "amountAfterFees": 130,
                    "amountBeforeFeesUsd": 1.037584,
                    "amountAfterFeesUsd": 0.999155,
                    "chargedFees": [
                        {
                            "id": "service_fee",
                            "type": "percentage",
                            "recipient": "platform",
                            "amount": 3.38
                        },
                        {
                            "id": "merchant_fee",
                            "type": "percentage",
                            "recipient": "merchant",
                            "amount": 1.35
                        }
                    ],
                    "chargedFeesUsd": [
                        {
                            "id": "service_fee",
                            "type": "percentage",
                            "recipient": "platform",
                            "amount": 0.025978
                        },
                        {
                            "id": "merchant_fee",
                            "type": "percentage",
                            "recipient": "merchant",
                            "amount": 0.010376
                        }
                    ],
                    "totalChargedFees": 4.73,
                    "totalChargedFeesUsd": 0.036354,
                    "exchangeRate": 130.11,
                    "exchangeRateAfterFees": 135.1142,
                    "chargedFeesPerRecipient": {
                        "platform": 3.38,
                        "merchant": 1.35
                    },
                    "chargedFeesPerRecipientUsd": {
                        "platform": 0.025978,
                        "merchant": 0.010376
                    },
                    "feeSettings": [
                        {
                            "id": "service_fee",
                            "recipient": "platform",
                            "type": "percentage",
                            "value": 2.5,
                            "min": 0,
                            "max": "Infinity"
                        },
                        {
                            "id": "merchant_fee",
                            "recipient": "merchant",
                            "type": "percentage",
                            "value": 1,
                            "min": 0,
                            "max": "Infinity"
                        }
                    ]
                },
                "providedFieldsToCreateOrder": {
                    "phoneNumber": "2348012345678",
                    "carrierCode": "ke_safaricom"
                },
                "transferInstructions": {
                    "type": "stk_push",
                    "intermediateActionAttempts": 1,
                    "intermediateActionMaxAttempts": 3,
                    "intermediateActionButtonText": "Retry USSD prompt initialization",
                    "intermediateActionNextAttemptAvailableAt": "2025-11-27T12:04:14.750Z",
                    "intermediateActionTimeoutMs": 60000,
                    "isIntermediateActionAvailable": true,
                    "forcePhoneNumberVerification": true,
                    "fieldsForIntermediateAction": [
                        {
                            "key": "otpCode",
                            "label": "OTP code",
                            "type": "number",
                            "required": true
                        }
                    ],
                    "instructionsText": "It is a sandbox offer. Confirm the transfer from your side and system will automatically confirm the transfer within 1 minute.",
                    "warningText": "Non-confirmed orders will be automatically canceled after 5 minutes.",
                    "transferDetails": [
                        {
                            "id": "amountToSend",
                            "label": "Amount to send",
                            "value": "135"
                        }
                    ],
                    "fieldsToConfirmOrder": []
                }
            },
            "payout": {
                "paymentChannel": "crypto",
                "currencyType": "crypto",
                "currencyCode": "POLYGON_USDT",
                "currencyDetails": {
                    "network": "POLYGON",
                    "asset": "USDT",
                    "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
                },
                "cashout": {
                    "amountBeforeFees": 1.000645,
                    "amountAfterFees": 1,
                    "amountBeforeFeesUsd": 1.000645,
                    "amountAfterFeesUsd": 1,
                    "chargedFees": [
                        {
                            "id": "gas",
                            "type": "flat_amount",
                            "recipient": "blockchain",
                            "amount": 0.000645
                        }
                    ],
                    "chargedFeesUsd": [
                        {
                            "id": "gas",
                            "type": "flat_amount",
                            "recipient": "blockchain",
                            "amount": 0.000645
                        }
                    ],
                    "totalChargedFees": 0.000645,
                    "totalChargedFeesUsd": 0.000645,
                    "exchangeRate": 1,
                    "exchangeRateAfterFees": 1.0006,
                    "chargedFeesPerRecipient": {
                        "blockchain": 0.000645
                    },
                    "chargedFeesPerRecipientUsd": {
                        "blockchain": 0.000645,
                        "platform": 0.025978,
                        "merchant": 0.010376
                    },
                    "feeSettings": [
                        {
                            "id": "gas",
                            "recipient": "blockchain",
                            "type": "flat_amount",
                            "value": 0.000645,
                            "min": 0,
                            "max": "Infinity"
                        }
                    ]
                },
                "providedFieldsToCreateOrder": {
                    "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
                }
            },
            "statusChangeLogs": [
                {
                    "newStatus": "deposit_canceled",
                    "date": "2025-11-27T12:03:20.472Z"
                }
            ],
            "createdAt": "2025-11-27T12:03:14.753Z",
            "updatedAt": "2025-11-27T12:03:20.473Z",
            "expiresAt": "2025-11-27T12:08:14.702Z"
        },
        {
            "_id": "69283d079061f4031ad1ba03",
            "countryIsoCode": "KE",
            "userId": "69283c0518613bc9de730cb4",
            "userEmail": "testuser+ke@fonbnk.com",
            "status": "payout_successful",
            "deposit": {
                "paymentChannel": "mobile_money",
                "currencyType": "fiat",
                "currencyCode": "KES",
                "currencyDetails": {
                    "countryIsoCode": "KE",
                    "carrier": {
                        "code": "ke_safaricom",
                        "name": "Safaricom Kenya",
                        "_id": "618e43914f57e07d255ff353"
                    }
                },
                "cashout": {
                    "amountBeforeFees": 135,
                    "amountAfterFees": 130,
                    "amountBeforeFeesUsd": 1.037823,
                    "amountAfterFeesUsd": 0.999385,
                    "chargedFees": [
                        {
                            "id": "service_fee",
                            "type": "percentage",
                            "recipient": "platform",
                            "amount": 3.38
                        },
                        {
                            "id": "merchant_fee",
                            "type": "percentage",
                            "recipient": "merchant",
                            "amount": 1.35
                        }
                    ],
                    "chargedFeesUsd": [
                        {
                            "id": "service_fee",
                            "type": "percentage",
                            "recipient": "platform",
                            "amount": 0.025984
                        },
                        {
                            "id": "merchant_fee",
                            "type": "percentage",
                            "recipient": "merchant",
                            "amount": 0.010378
                        }
                    ],
                    "totalChargedFees": 4.73,
                    "totalChargedFeesUsd": 0.036362,
                    "exchangeRate": 130.08,
                    "exchangeRateAfterFees": 135.0831,
                    "chargedFeesPerRecipient": {
                        "platform": 3.38,
                        "merchant": 1.35
                    },
                    "chargedFeesPerRecipientUsd": {
                        "platform": 0.025984,
                        "merchant": 0.010378
                    },
                    "feeSettings": [
                        {
                            "id": "service_fee",
                            "recipient": "platform",
                            "type": "percentage",
                            "value": 2.5,
                            "min": 0,
                            "max": "Infinity"
                        },
                        {
                            "id": "merchant_fee",
                            "recipient": "merchant",
                            "type": "percentage",
                            "value": 1,
                            "min": 0,
                            "max": "Infinity"
                        }
                    ]
                },
                "providedFieldsToCreateOrder": {
                    "phoneNumber": "2348012345678",
                    "carrierCode": "ke_safaricom"
                },
                "providedFieldsToConfirmOrder": {},
                "transferInstructions": {
                    "type": "otp_stk_push",
                    "intermediateActionAttempts": 2,
                    "intermediateActionMaxAttempts": 3,
                    "intermediateActionButtonText": "Verify OTP code",
                    "intermediateActionNextAttemptAvailableAt": "2025-11-27T12:00:14.390Z",
                    "intermediateActionTimeoutMs": 30000,
                    "isIntermediateActionAvailable": true,
                    "forcePhoneNumberVerification": true,
                    "fieldsForIntermediateAction": [
                        {
                            "key": "otpCode",
                            "label": "OTP code",
                            "type": "number",
                            "required": true
                        }
                    ],
                    "instructionsText": "It is a sandbox offer. Use 123456 as OTP code and confirm the transfer from your side and system will automatically confirm the transfer within 1 minute.",
                    "warningText": "Non-confirmed orders will be automatically canceled after 5 minutes.",
                    "transferDetails": [
                        {
                            "id": "amountToSend",
                            "label": "Amount to send",
                            "value": "135"
                        }
                    ],
                    "fieldsToConfirmOrder": [],
                    "intermediateActionRequired": true,
                    "intermediateActionExecuted": true,
                    "otpChannel": "sms"
                }
            },
            "payout": {
                "paymentChannel": "crypto",
                "currencyType": "crypto",
                "currencyCode": "POLYGON_USDT",
                "currencyDetails": {
                    "network": "POLYGON",
                    "asset": "USDT",
                    "contractAddress": "0x3b3a06b48119c035a2e86afdb69d9ad930643b3d"
                },
                "cashout": {
                    "amountBeforeFees": 1.000645,
                    "amountAfterFees": 1,
                    "amountBeforeFeesUsd": 1.000645,
                    "amountAfterFeesUsd": 1,
                    "chargedFees": [
                        {
                            "id": "gas",
                            "type": "flat_amount",
                            "recipient": "blockchain",
                            "amount": 0.000645
                        }
                    ],
                    "chargedFeesUsd": [
                        {
                            "id": "gas",
                            "type": "flat_amount",
                            "recipient": "blockchain",
                            "amount": 0.000645
                        }
                    ],
                    "totalChargedFees": 0.000645,
                    "totalChargedFeesUsd": 0.000645,
                    "exchangeRate": 1,
                    "exchangeRateAfterFees": 1.0006,
                    "chargedFeesPerRecipient": {
                        "blockchain": 0.000645
                    },
                    "chargedFeesPerRecipientUsd": {
                        "blockchain": 0.000645,
                        "platform": 0.025984,
                        "merchant": 0.010378
                    },
                    "feeSettings": [
                        {
                            "id": "gas",
                            "recipient": "blockchain",
                            "type": "flat_amount",
                            "value": 0.000645,
                            "min": 0,
                            "max": "Infinity"
                        }
                    ]
                },
                "providedFieldsToCreateOrder": {
                    "blockchainWalletAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20"
                },
                "transaction": {
                    "meta": {
                        "transactionHash": "0xe168c39bf7165c0eaa88e4df1e21e987666e44f11f2bea6f9be1c145f382dade"
                    }
                }
            },
            "statusChangeLogs": [
                {
                    "oldStatus": "deposit_awaiting",
                    "newStatus": "deposit_validating",
                    "date": "2025-11-27T12:01:43.660Z"
                },
                {
                    "oldStatus": "deposit_validating",
                    "newStatus": "deposit_successful",
                    "date": "2025-11-27T12:02:00.770Z"
                },
                {
                    "oldStatus": "deposit_successful",
                    "newStatus": "payout_pending",
                    "date": "2025-11-27T12:02:01.306Z"
                },
                {
                    "oldStatus": "payout_pending",
                    "newStatus": "payout_successful",
                    "date": "2025-11-27T12:02:19.053Z"
                }
            ],
            "createdAt": "2025-11-27T11:59:03.754Z",
            "updatedAt": "2025-11-27T12:02:19.124Z",
            "expiresAt": "2025-11-27T12:04:03.673Z"
        }
    ]
}
```

{% endcode %}


# Get user KYC state

## <mark style="color:$success;">\[GET]</mark> /api/v2/user/kyc

Returns a KYC state of a user. It also returns KYC rules and available documents for the user’s country.

Request query params type:

```typescript
type QueryParams = {
    userEmail: string, //required
    countryIsoCode: string //required
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/user/kyc?userEmail=testuser_ng@fonbnk.com&countryIsoCode=NG
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  passedKycType?: KycType;
  reachedKycLimit: boolean;
  currentKycType?: KycType;
  currentKycStatus?: KycStatus;
  currentKycStatusDescription?: string;
  kycDocuments: KycDocument[],
  kycRules: {
    operationType: OperationType;
    currencyType: CurrencyType;
    min: number; // USD
    max: number | 'Infinity';
    type: KycType;
  }[]
}
```

{% endcode %}

Response example:

```json
{
    "passedKycType": "basic",
    "reachedKycLimit": false,
    "currentKycType": "basic",
    "currentKycStatus": "approved",
    "currentKycStatusDescription": "Exact Match",
    "kycDocuments": [
        {
            "_id": "67da93c0dfd3a00f3380b857",
            "title": "Driving License",
            "value": "DRIVERS_LICENSE",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c0dfd3a00f3380b859",
            "title": "National IDs, Consular IDs & Diplomat IDs",
            "value": "IDENTITY_CARD",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c0dfd3a00f3380b85b",
            "title": "Passports",
            "value": "PASSPORT",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c0dfd3a00f3380b85d",
            "title": "Registration certificate",
            "value": "REGISTRATION_CERTIFICATE",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c1dfd3a00f3380b85f",
            "title": "Residency permits, Refugee IDs & Residency cards",
            "value": "RESIDENT_ID",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c1dfd3a00f3380b861",
            "title": "Border crossing documents & Visas",
            "value": "TRAVEL_DOC",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da93c1dfd3a00f3380b863",
            "title": "Voter's Identity Card",
            "value": "VOTER_ID",
            "type": "advanced",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "images",
                    "type": "smile-identity-images",
                    "label": "Verification images",
                    "required": true
                }
            ]
        },
        {
            "_id": "67da909b739fc481aa525c43",
            "title": "Voter ID",
            "value": "VOTER_ID",
            "type": "basic",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "id_number",
                    "type": "string",
                    "label": "ID number",
                    "required": true,
                    "format": "0000000000000000000",
                    "regexp": "^[a-zA-Z0-9 ]{9,29}$",
                    "regexpFlags": "i"
                }
            ]
        },
        {
            "_id": "67da909b739fc481aa525c45",
            "title": "BVN",
            "value": "BVN",
            "type": "basic",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "id_number",
                    "type": "string",
                    "label": "BVN Number",
                    "required": true,
                    "format": "00000000000",
                    "regexp": "^[0-9]{11}$"
                }
            ]
        },
        {
            "_id": "67da909b739fc481aa525c47",
            "title": "NIN V2",
            "value": "NIN_V2",
            "type": "basic",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "id_number",
                    "type": "string",
                    "label": "NIN V2 Number",
                    "required": true,
                    "format": "00000000000",
                    "regexp": "^[0-9]{11}$"
                }
            ]
        },
        {
            "_id": "67da909b739fc481aa525c49",
            "title": "NIN SLIP",
            "value": "NIN_SLIP",
            "type": "basic",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "id_number",
                    "type": "string",
                    "label": "NIN SLIP Number",
                    "required": true,
                    "format": "00000000000",
                    "regexp": "^[0-9]{11}$"
                }
            ]
        },
        {
            "_id": "67da909b739fc481aa525c4b",
            "title": "Drivers license",
            "value": "DRIVERS_LICENSE",
            "type": "basic",
            "requiredFields": [
                {
                    "key": "first_name",
                    "type": "string",
                    "label": "First Name",
                    "required": true
                },
                {
                    "key": "last_name",
                    "type": "string",
                    "label": "Last Name",
                    "required": true
                },
                {
                    "key": "dob",
                    "type": "date",
                    "label": "Date of birth",
                    "required": true
                },
                {
                    "key": "id_number",
                    "type": "string",
                    "label": "ID number",
                    "required": true,
                    "regexp": "^[a-zA-Z]{3}([ -]{1})?[A-Z0-9]{6,12}$",
                    "regexpFlags": "i",
                    "format": "ABC000000000"
                }
            ]
        }
    ],
    "kycRules": [
        {
            "operationType": "deposit",
            "currencyType": "fiat",
            "min": 2,
            "max": 20,
            "type": "basic"
        },
        {
            "operationType": "deposit",
            "currencyType": "fiat",
            "min": 20,
            "max": "Infinity",
            "type": "advanced"
        },
        {
            "operationType": "deposit",
            "currencyType": "crypto",
            "min": 2,
            "max": 20,
            "type": "basic"
        },
        {
            "operationType": "deposit",
            "currencyType": "crypto",
            "min": 20,
            "max": "Infinity",
            "type": "advanced"
        }
    ]
}
```


# Submit user KYC

## <mark style="color:$warning;">\[POST]</mark> /api/v2/user/kyc

Submits KYC documents for a user.

Request body type:

```typescript
type RequestBody = {
  userEmail: string;
  documentId: string;
  userFields: Record<string, any>;
}
```

Request body example (basic KYC):

```json
{
    "userEmail": "testuser_ng@fonbnk.com",
    "documentId": "67da909b739fc481aa525c43",
    "userFields": {
        "first_name": "John",
        "last_name": "Doe",
        "dob": "1990-01-01",
        "id_number": "A123456789"
    }
}
```

For advanced kyc, an array of images must be provided. Each image object should contain an <mark style="color:$danger;">"image\_type\_id"</mark> field where  with one of these values:

* 0 - the type of an image is "selfie"
* 1 - the type of an image is "front side of the document"
* 2 - the type of an image is "back side of the document".

The <mark style="color:$danger;">"image"</mark> field should contain either a public URL to an image or a base64  string of an encoded image.

Request body example (advanced KYC):

```json
{
  "userEmail": "testuser_ng@fonbnk.com",
  "countryIsoCode": "NG",
  "documentId": "67da93c0dfd3a00f3380b857",
  "userFields": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-01-01",
    "images": [
      { "image_type_id": 0, "image": "https://cdn.com/selfie.jpg" },
      { "image_type_id": 1, "image": "https://cdn.com/front.jpg" },
      { "image_type_id": 5, "image": "https://cdn.com/back.jpg" }
    ]
  }
}
```

Response is the same as ge


# Merchant balance

## Crypto merchant balance

These endpoints let you top up your USD merchant balance with on-chain crypto.

They also let you withdraw your USD merchant balance back out as crypto.

All routes live under `/api/v2/merchant-balance/`.

All requests use the standard signed-request authentication flow. See [Signing requests](/server-to-server/signing-requests).

### Availability

These features are gated.

Fonbnk must enable them for your account.

When a feature is off, the endpoint returns `403` with one of these codes:

* `MERCHANT_BALANCE_CRYPTO_DEPOSIT_DISABLED`
* `MERCHANT_BALANCE_CRYPTO_WITHDRAW_DISABLED`

Creating a deposit or withdrawal also requires a verified merchant organization.

Otherwise, the endpoint returns `403 MERCHANT_ORG_NOT_VERIFIED`.

### Deposit lifecycle

A crypto deposit moves through these statuses:

* `deposit_awaiting` — order created. Send crypto to `depositAddress`.
* `deposit_validating` — transaction hash submitted. On-chain confirmation is pending.
* `deposit_successful` — deposit confirmed.
* `payout_pending` — USD credit is being processed.
* `payout_successful` — USD balance is credited.

You can cancel a deposit only while it is still `deposit_awaiting`.

### Withdrawal lifecycle

On create, the USD `amount` is deducted from your balance.

The order starts in `deposit_successful` and waits for Fonbnk admin approval.

After approval, it moves to `payout_pending`, then `payout_successful` when crypto is sent to your `address`.

If the withdrawal is rejected, or canceled while still awaiting approval, the order moves to `deposit_canceled` and the deducted balance is restored.

See [Order statuses](/server-to-server/order-statuses).

### Asset selection

Call the relevant `.../options` endpoint to discover supported `(network, asset)` pairs.

Call the matching `.../limits` endpoint for the authoritative min and max values.

Unsupported pairs are rejected up front:

* `MERCHANT_BALANCE_DEPOSIT_ASSET_UNSUPPORTED`
* `MERCHANT_BALANCE_WITHDRAW_ASSET_UNSUPPORTED`


# Get merchant balances

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance

Returns the amount of funds on a merchant balance

{% code title="Response type:" overflow="wrap" expandable="true" %}

```typescript
type Response = {
    USD: number
}
```

{% endcode %}

{% code title="Response example:" overflow="wrap" expandable="true" %}

```json
{
  "USD": 545
}
```

{% endcode %}


# Get crypto deposit options

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/deposit/options

Returns the `(network, asset)` pairs you can deposit, with display metadata and formatting precision for the amount input.

Amount limits are not included here.

Use *Get crypto deposit limits* for the authoritative min and max values of a selected pair.

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  currencyCode: string;   // e.g. "POLYGON_USDC"
  network: string;        // e.g. "POLYGON"
  asset: string;          // e.g. "USDC"
  networkTitle: string;
  assetTitle: string;
  networkIcon: string;
  assetIcon: string;
  exchangeRate: number;   // crypto -> USD rate used to credit the balance
  precision: number;      // decimals to use when formatting the amount
}[]
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyCode": "POLYGON_USDC",
    "network": "POLYGON",
    "asset": "USDC",
    "networkTitle": "Polygon",
    "assetTitle": "USD Coin",
    "networkIcon": "https://assets.fonbnk.com/networks/polygon.svg",
    "assetIcon": "https://assets.fonbnk.com/assets/usdc.svg",
    "exchangeRate": 1,
    "precision": 6
  }
]
```

{% endcode %}


# Get crypto deposit limits

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/deposit/limits

Returns the authoritative min and max values for a deposit of the selected `(network, asset)` pair.

Values are returned in both crypto-native and USD terms, already tightened by your resolved limit rules.

The `deposit` leg is the crypto you send.

The `payout` leg is the USD credited to your balance.

Request query params type:

```typescript
type QueryParams = {
  network: string;   // e.g. "POLYGON"
  asset: string;     // e.g. "USDC"
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/deposit/limits?network=POLYGON&asset=USDC
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  deposit: {           // the crypto you send
    min: number;       // in crypto native units
    max: number;
    minUsd: number;
    maxUsd: number;
    step: number;
    supportsDecimals: boolean;
  };
  payout: {            // the USD credited to your balance
    min: number;
    max: number;
    minUsd: number;
    maxUsd: number;
    step: number;
    supportsDecimals: boolean;
  };
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "deposit": {
    "min": 1,
    "max": 5000,
    "minUsd": 1,
    "maxUsd": 5000,
    "step": 0.000001,
    "supportsDecimals": true
  },
  "payout": {
    "min": 1,
    "max": 5000,
    "minUsd": 1,
    "maxUsd": 5000,
    "step": 0.01,
    "supportsDecimals": true
  }
}
```

{% endcode %}


# Create crypto deposit

## <mark style="color:$warning;">\[POST]</mark> /api/v2/merchant-balance/deposit

Opens a crypto deposit.

Send `amount` of the selected asset on-chain to the returned `address`.

Then submit the transaction hash with *Confirm crypto deposit*.

Once confirmed, your USD merchant balance is credited 1:1 from the asset's USD price.

Requires the crypto deposit feature to be enabled and a verified merchant organization.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  network: string;   // e.g. "POLYGON"
  asset: string;     // e.g. "USDC"
  amount: number;    // amount of crypto to send in native units
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "network": "POLYGON",
  "asset": "USDC",
  "amount": 100
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;       // "deposit_awaiting" on create
  network: string;
  asset: string;
  currencyCode: string;      // e.g. "POLYGON_USDC"
  address: string;    // send the crypto here
  amount: number;    // crypto amount to send
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198",
  "status": "deposit_awaiting",
  "network": "POLYGON",
  "asset": "USDC",
  "currencyCode": "POLYGON_USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "amount": 100,
  "expiresAt": "2025-11-27T10:29:56.167Z",
  "createdAt": "2025-11-27T09:29:56.167Z",
  "updatedAt": "2025-11-27T09:29:56.167Z"
}
```

{% endcode %}


# Confirm crypto deposit

## <mark style="color:$warning;">\[POST]</mark> /api/v2/merchant-balance/deposit/confirm

Submits the on-chain transaction hash for a previously created deposit.

The order moves to `deposit_validating` and is credited once the transfer is confirmed on-chain.

The hash is single-use.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;   // the deposit order id
  hash: string;      // the on-chain transaction hash
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198",
  "hash": "0x9f8b1a2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff"
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;       // "deposit_validating" after confirm
  network: string;
  asset: string;
  currencyCode: string;
  address: string;
  amount: number;
  hash?: string,
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198",
  "status": "deposit_validating",
  "network": "POLYGON",
  "asset": "USDC",
  "currencyCode": "POLYGON_USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "amount": 100,
  "expiresAt": "2025-11-27T10:29:56.167Z",
  "createdAt": "2025-11-27T09:29:56.167Z",
  "updatedAt": "2025-11-27T09:31:12.402Z"
}
```

{% endcode %}


# Cancel crypto deposit

## <mark style="color:$warning;">\[POST]</mark> /api/v2/merchant-balance/deposit/cancel

Cancels a deposit you created but have not paid for yet.

This is valid only while the order is still `deposit_awaiting`.

No crypto must have been observed on-chain.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;   // the deposit order id
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198"
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;       // "deposit_canceled" after cancel
  network: string;
  asset: string;
  currencyCode: string;
  address: string;
  amount: number;
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198",
  "status": "deposit_canceled",
  "network": "POLYGON",
  "asset": "USDC",
  "currencyCode": "POLYGON_USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "amount": 100,
  "expiresAt": "2025-11-27T10:29:56.167Z",
  "createdAt": "2025-11-27T09:29:56.167Z",
  "updatedAt": "2025-11-27T09:35:00.000Z"
}
```

{% endcode %}


# Get crypto deposit

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/deposit

Fetches a single deposit order by id for status polling.

Request query params type:

```typescript
type QueryParams = {
  orderId: string;   // the deposit order id
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/deposit?orderId=69281d944a1db009177f0198
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;
  network: string;
  asset: string;
  currencyCode: string;
  address: string;
  amount: number;
  hash?: string;
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281d944a1db009177f0198",
  "status": "payout_successful",
  "network": "POLYGON",
  "asset": "USDC",
  "currencyCode": "POLYGON_USDC",
  "depositAddress": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "expectedAmount": 100,
  "expiresAt": "2025-11-27T10:29:56.167Z",
  "createdAt": "2025-11-27T09:29:56.167Z",
  "updatedAt": "2025-11-27T09:33:40.118Z"
}
```

{% endcode %}


# Get crypto deposits

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/deposits

Returns a paginated list of your crypto deposit orders, newest first.

Request query params type:

```typescript
type QueryParams = {
  page?: number;    // default 1
  limit?: number;   // default 50, max 200
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/deposits?page=1&limit=50
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  docs: {
    orderId: string;
    status: OrderStatus;
    network: string;
    asset: string;
    currencyCode: string;
    address: string;
    amount: number;
    expiresAt: Date;
    createdAt: Date;
    updatedAt: Date;
  }[];
  totalDocs: number;
  limit: number;
  page: number;
  totalPages: number;
  offset: number;
  hasPrevPage: boolean;
  hasNextPage: boolean;
  prevPage: number | null;
  nextPage: number | null;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "docs": [
    {
      "orderId": "69281d944a1db009177f0198",
      "status": "payout_successful",
      "network": "POLYGON",
      "asset": "USDC",
      "currencyCode": "POLYGON_USDC",
      "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
      "amount": 100,
      "expiresAt": "2025-11-27T10:29:56.167Z",
      "createdAt": "2025-11-27T09:29:56.167Z",
      "updatedAt": "2025-11-27T09:33:40.118Z"
    }
  ],
  "totalDocs": 1,
  "limit": 50,
  "page": 1,
  "totalPages": 1,
  "offset": 0,
  "hasPrevPage": false,
  "hasNextPage": false,
  "prevPage": null,
  "nextPage": null
}
```

{% endcode %}


# Get crypto withdrawal options

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/withdrawal/options

Returns the `(network, asset)` pairs you can withdraw to, with display metadata, formatting precision, and withdrawal `feeSettings` applied on broadcast.

Amount limits are not included here.

Use *Get crypto withdrawal limits* for the authoritative min and max values.

Request: no body or query params.

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  currencyCode: string;   // e.g. "POLYGON_USDC"
  network: string;
  asset: string;
  networkTitle: string;
  assetTitle: string;
  networkIcon: string;
  assetIcon: string;
  exchangeRate: number;   // USD -> crypto rate
  precision: number;
  feeSettings: {
    id: string;
    recipient: string;
    type: string;         // e.g. "flat_amount"
    value: number;
    min: number;
    max: number | "Infinity";
  }[];
}[]
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
[
  {
    "currencyCode": "POLYGON_USDC",
    "network": "POLYGON",
    "asset": "USDC",
    "networkTitle": "Polygon",
    "assetTitle": "USD Coin",
    "networkIcon": "https://assets.fonbnk.com/networks/polygon.svg",
    "assetIcon": "https://assets.fonbnk.com/assets/usdc.svg",
    "exchangeRate": 1,
    "precision": 6,
    "feeSettings": [
      {
        "id": "gas",
        "recipient": "blockchain",
        "type": "flat_amount",
        "value": 0.000645,
        "min": 0,
        "max": "Infinity"
      }
    ]
  }
]
```

{% endcode %}


# Get crypto withdrawal limits

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/withdrawal/limits

Returns the authoritative min and max values for a withdrawal to the selected `(network, asset)` pair.

Values are already tightened by your resolved limit rules.

The `deposit` leg is the USD deducted from your balance.

The `payout` leg is the crypto you receive.

Request query params type:

```typescript
type QueryParams = {
  network: string;   // e.g. "POLYGON"
  asset: string;     // e.g. "USDC"
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/withdrawal/limits?network=POLYGON&asset=USDC
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  deposit: {           // the USD deducted from your balance
    min: number;
    max: number;
    minUsd: number;
    maxUsd: number;
    step: number;
    supportsDecimals: boolean;
  };
  payout: {            // the crypto you receive
    min: number;
    max: number;
    minUsd: number;
    maxUsd: number;
    step: number;
    supportsDecimals: boolean;
  };
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "deposit": {
    "min": 1,
    "max": 5000,
    "minUsd": 1,
    "maxUsd": 5000,
    "step": 0.01,
    "supportsDecimals": true
  },
  "payout": {
    "min": 1,
    "max": 5000,
    "minUsd": 1,
    "maxUsd": 5000,
    "step": 0.000001,
    "supportsDecimals": true
  }
}
```

{% endcode %}


# Create crypto withdrawal

## <mark style="color:$warning;">\[POST]</mark> /api/v2/merchant-balance/withdrawal

Withdraws `amount` USD from your merchant balance to `address` as crypto.

On create, the USD amount is deducted and the order is parked awaiting Fonbnk admin approval before any crypto is broadcast.

The returned order is in `deposit_successful`.

If the withdrawal is later rejected by an admin, or canceled while still parked, the deducted balance is restored.

Requires the crypto withdrawal feature to be enabled and a verified merchant organization.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  network: string;   // e.g. "POLYGON"
  asset: string;     // e.g. "USDC"
  address: string;   // destination crypto address
  amount: number;    // USD amount to withdraw
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "network": "POLYGON",
  "asset": "USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "amount": 100
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;       // "deposit_successful" while awaiting approval
  network: string;
  asset: string;
  address: string;           // destination crypto address
  currencyCode: string;      // e.g. "POLYGON_USDC"
  amount: number;            // USD amount deducted
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281f0aa263ba8d44fad3b2",
  "status": "deposit_successful",
  "network": "POLYGON",
  "asset": "USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "currencyCode": "POLYGON_USDC",
  "amount": 100,
  "createdAt": "2025-11-27T09:40:10.001Z",
  "updatedAt": "2025-11-27T09:40:10.500Z"
}
```

{% endcode %}


# Cancel crypto withdrawal

## <mark style="color:$warning;">\[POST]</mark> /api/v2/merchant-balance/withdrawal/cancel

Cancels a withdrawal you submitted while it is still awaiting admin approval. The deducted balance is restored and the order is closed with `deposit_canceled`.

Once an admin has approved or rejected the withdrawal, you can no longer cancel it here.

Request body type:

{% code overflow="wrap" expandable="true" %}

```typescript
type RequestBody = {
  orderId: string;    // the withdrawal order id
  reason?: string;    // optional, max 500 chars
}
```

{% endcode %}

Request body example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281f0aa263ba8d44fad3b2",
  "reason": "Wrong destination address"
}
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;       // "deposit_canceled" after cancel
  network: string;
  asset: string;
  address: string;
  currencyCode: string;
  amount: number;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281f0aa263ba8d44fad3b2",
  "status": "deposit_canceled",
  "network": "POLYGON",
  "asset": "USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "currencyCode": "POLYGON_USDC",
  "amount": 100,
  "createdAt": "2025-11-27T09:40:10.001Z",
  "updatedAt": "2025-11-27T09:42:33.220Z"
}
```

{% endcode %}


# Get crypto withdrawal

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/withdrawal

Fetches a single withdrawal order by id, for status polling.

Request query params type:

```typescript
type QueryParams = {
  orderId: string;   // the withdrawal order id
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/withdrawal?orderId=69281f0aa263ba8d44fad3b2
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  orderId: string;
  status: OrderStatus;
  network: string;
  asset: string;
  address: string;
  currencyCode: string;
  amount: number;
  hash?: string;
  createdAt: Date;
  updatedAt: Date;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "orderId": "69281f0aa263ba8d44fad3b2",
  "status": "payout_successful",
  "network": "POLYGON",
  "asset": "USDC",
  "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
  "currencyCode": "POLYGON_USDC",
  "amount": 100,
  "createdAt": "2025-11-27T09:40:10.001Z",
  "updatedAt": "2025-11-27T10:05:18.770Z"
}
```

{% endcode %}


# Get crypto withdrawals

## <mark style="color:$success;">\[GET]</mark> /api/v2/merchant-balance/withdrawals

Paginated list of your crypto withdrawal orders, newest first.

Request query params type:

```typescript
type QueryParams = {
  page?: number;    // default 1
  limit?: number;   // default 50, max 200
}
```

Request URL example:

{% code overflow="wrap" %}

```
GET /api/v2/merchant-balance/withdrawals?page=1&limit=50
```

{% endcode %}

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  docs: {
    orderId: string;
    status: OrderStatus;
    network: string;
    asset: string;
    address: string;
    currencyCode: string;
    amount: number;
    hash?: string;
    createdAt: Date;
    updatedAt: Date;
  }[];
  totalDocs: number;
  limit: number;
  page: number;
  totalPages: number;
  offset: number;
  hasPrevPage: boolean;
  hasNextPage: boolean;
  prevPage: number | null;
  nextPage: number | null;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
  "docs": [
    {
      "orderId": "69281f0aa263ba8d44fad3b2",
      "status": "payout_successful",
      "network": "POLYGON",
      "asset": "USDC",
      "address": "0x5b7ae3c6c87f4a3f94b35c77233b13191ebfad20",
      "currencyCode": "POLYGON_USDC",
      "amount": 100,
      "createdAt": "2025-11-27T09:40:10.001Z",
      "updatedAt": "2025-11-27T10:05:18.770Z"
    }
  ],
  "totalDocs": 1,
  "limit": 50,
  "page": 1,
  "totalPages": 1,
  "offset": 0,
  "hasPrevPage": false,
  "hasNextPage": false,
  "prevPage": null,
  "nextPage": null
}
```

{% endcode %}


# Generate user auth tokens

## <mark style="color:$warning;">\[POST]</mark> /api/v2/user/tokens

Returns an access and a refresh token to log the user into the pay widget automatically. Used for screens skipping in the Pay Widget.

Request body type:

```typescript
type RequestBody = {
  email: string;
  countryIsoCode: string;
};
```

Request body example:

```json
{
  "email": "testuser+ng@fonbnk.com",
  "countryIsoCode": "NG"
}
```

Response type:

{% code overflow="wrap" expandable="true" %}

```typescript
type Response = {
  accessToken: string;
  refreshToken: string;
}
```

{% endcode %}

Response example:

{% code overflow="wrap" expandable="true" %}

```json
{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyRGF0YSI6eyJ1c2VybmFtZSI6InVzZXIjLWhsRjNmek8iLCJfaWQiOiI2OTI4MWQ0ZjE4NjEzYmM5ZGU3MmVkOGUiLCJpc0RldlVzZXIiOmZhbHNlLCJjb3VudHJ5SXNvQ29kZSI6Ik5HIiwiaXNBbWJhc3NhZG9yIjpmYWxzZSwiZW1haWwiOiJ0ZXN0dXNlcituZ0Bmb25ibmsuY29tIn0sInR5cGUiOiJhY2Nlc3MiLCJ1aWQiOiIwMUtDNk02TTJEVkZSSzkxNUtZUTdQQkJBUCIsImlhdCI6MTc2NTQ1NDA3MywiZXhwIjoxNzY1NDU0OTczfQ.zRI7_Dins7VskO9epKJWIlZLwxPMiyKG8Fq_hjXOTAc",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyRGF0YSI6eyJ1c2VybmFtZSI6InVzZXIjLWhsRjNmek8iLCJfaWQiOiI2OTI4MWQ0ZjE4NjEzYmM5ZGU3MmVkOGUiLCJpc0RldlVzZXIiOmZhbHNlLCJjb3VudHJ5SXNvQ29kZSI6Ik5HIiwiaXNBbWJhc3NhZG9yIjpmYWxzZSwiZW1haWwiOiJ0ZXN0dXNlcituZ0Bmb25ibmsuY29tIn0sInR5cGUiOiJyZWZyZXNoIiwidWlkIjoiMDFLQzZNNk0ySjc2SEY0WlgzUFRQRTUwQjAiLCJpYXQiOjE3NjU0NTQwNzMsImV4cCI6MTc2NjY2MzY3M30.P1peJxGy7HivuubXbUEnqAW_3vbhem41zMU6v75uI6U"
}
```

{% endcode %}


# Types

Types used in the code examples

{% code overflow="wrap" expandable="true" %}

```typescript
enum PaymentChannel {
  BANK = 'bank',
  AIRTIME = 'airtime',
  MOBILE_MONEY = 'mobile_money',
  PAYBILL = 'paybill',
  BUY_GOODS = 'buy_goods',
  MERCHANT_BALANCE = 'merchant_balance',
  CRYPTO = 'crypto',
}

enum CurrencyType {
  FIAT = 'fiat',
  CRYPTO = 'crypto',
  MERCHANT_BALANCE = 'merchant_balance',
}

type OrderCurrencyDetails =
  | OrderCryptoDetails
  | OrderFiatDetails
  | OrderMerchantDetails;

type OrderCryptoDetails = {
  network: string;
  asset: string;
  contractAddress: string;
};

type OrderFiatDetails = {
  countryIsoCode: string;
  carriers?: { code: string; name: string; }[];
}

type OrderMerchantDetails = { merchantName: string }

type Cashout = {
  exchangeRate: number;
  feeSettings: FeeSetting[];
  exchangeRateAfterFees: number;
  amountBeforeFees: number;
  amountAfterFees: number;
  amountBeforeFeesUsd: number;
  amountAfterFeesUsd: number;
  chargedFees: ChargedFee[];
  chargedFeesUsd: ChargedFee[];
  totalChargedFees: number;
  totalChargedFeesUsd: number;
  chargedFeesPerRecipient: Partial<Record<FeeRecipient, number>>;
  chargedFeesPerRecipientUsd: Partial<Record<FeeRecipient, number>>;
};

type FeeSetting =
  | {
  id: string;
  recipient: FeeRecipient;
  type: FeeType.FLAT_AMOUNT;
  value: number;
  min: number;
  max: number | 'Infinity'
}
  | {
  id: string;
  recipient: FeeRecipient;
  type: FeeType.PERCENTAGE;
  value: number;
  min: number;
  max: number | 'Infinity';
  minCap?: number;
  maxCap?: number
};

type ChargedFee = { id: string; type: FeeType; recipient: FeeRecipient; amount: number };

enum FeeRecipient { MERCHANT = 'merchant', PROVIDER = 'provider', PLATFORM = 'platform', BLOCKCHAIN = 'blockchain' }

enum FeeType { PERCENTAGE = 'percentage', FLAT_AMOUNT = 'flat_amount' }

type TransferInstructions =
  | ManualTransferInstructions
  | RedirectTransferInstructions
  | StkPushTransferInstructions
  | OtpStkPushTransferInstructions;

type ManualTransferInstructions = {
  type: TransferType.MANUAL;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type RedirectTransferInstructions = {
  type: TransferType.REDIRECT;
  paymentUrl: string;
  redirectedToPaymentUrl: boolean;
  intermediateActionButtonText: string;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type StkPushTransferInstructions = {
  type: TransferType.STK_PUSH;
  isIntermediateActionAvailable: boolean;
  intermediateActionButtonText: string;
  intermediateActionMaxAttempts: number;
  intermediateActionAttempts: number;
  intermediateActionNextAttemptAvailableAt: Date;
  intermediateActionTimeoutMs: number;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type OtpStkPushTransferInstructions = {
  type: TransferType.OTP_STK_PUSH;
  isIntermediateActionAvailable: boolean;
  intermediateActionButtonText: string;
  intermediateActionMaxAttempts: number;
  intermediateActionAttempts: number;
  intermediateActionNextAttemptAvailableAt: Date;
  intermediateActionTimeoutMs: number;
  fieldsForIntermediateAction: RequiredField[];
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

enum TransferType {
  MANUAL = 'manual',
  REDIRECT = 'redirect',
  STK_PUSH = 'stk_push',
  OTP_STK_PUSH = 'otp_stk_push',
}

type TransferDetail = { id: TransferDetailId; label: string; description?: string; value?: string };

enum TransferDetailId {
  RECIPIENT_WALLET_ADDRESS = 'recipientWalletAddress',
  SENDER_WALLET_ADDRESS = 'senderWalletAddress',
  AMOUNT_TO_SEND = 'amountToSend',
  CRYPTO_TRANSACTION_REQUEST_ADDITIONAL_DATA = 'cryptoTransactionRequestAdditionalData',
  RECIPIENT_BANK_NAME = 'recipientBankName',
  RECIPIENT_BANK_ACCOUNT_NUMBER = 'recipientBankAccountNumber',
  RECIPIENT_BANK_ACCOUNT_NAME = 'recipientBankAccountName',
  RECIPIENT_PHONE_NUMBER = 'recipientPhoneNumber',
  BANK_TRANSFER_NARRATION = 'bankTransferNarration',
}

type RequiredField = {
  key: string;
  type: FieldType;
  label: string;
  required: boolean;
  options?: { value: string; label: string }[];
  defaultValue?: string;
};

enum FieldType {
  NUMBER = 'number',
  STRING = 'string',
  DATE = 'date',
  BOOLEAN = 'boolean',
  EMAIL = 'email',
  PHONE = 'phone',
  ENUM = 'enum'
}

enum OrderStatus {
  DEPOSIT_AWAITING = 'deposit_awaiting', // waiting for user to pay
  DEPOSIT_EXPIRED = 'deposit_expired', // user did not pay in time
  DEPOSIT_CANCELED = 'deposit_canceled', // user canceled the order
  DEPOSIT_VALIDATING = 'deposit_validating', // order confirmed by a user, waiting for confirmation
  DEPOSIT_INVALID = 'deposit_invalid', // deposit failed or was invalid
  DEPOSIT_SUCCESSFUL = 'deposit_successful', // deposit validated successfully
  PAYOUT_PENDING = 'payout_pending', // payout in progress
  PAYOUT_SUCCESSFUL = 'payout_successful', // user received funds
  PAYOUT_FAILED = 'payout_failed', 
  REFUND_PENDING = 'refund_pending', 
  REFUND_SUCCESSFUL = 'refund_successful',
  REFUND_FAILED = 'refund_failed',
}

enum KycType { BASIC = 'basic', ADVANCED = 'advanced' }

enum KycStatus { INITIATED = 'initiated', APPROVED = 'approved', REJECTED = 'rejected', INVALID = 'invalid' }

type KycDocument = { _id: string; title: string; value: string; type: KycType; requiredFields: DocumentField[] }

type DocumentField =
  | {
  key: string;
  type: 'number' | 'string' | 'date' | 'boolean' | 'email' | 'phone' | 'smile-identity-images';
  label: string;
  required: boolean;
  defaultValue?: string | number | boolean;
  regexp?: string;
  regexpFlags?: string;
  format?: string
}
  | {
  key: string;
  type: 'enum';
  label: string;
  required: boolean;
  options: { value: string; label: string }[];
  defaultValue?: string;
  regexp?: string;
  regexpFlags?: string;
  format?: string
};

enum OperationType { DEPOSIT = 'deposit', PAYOUT = 'payout' }

```

{% endcode %}


