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

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Accept USDT and settle in naira

> Receive USDT on-chain and settle it as naira in a local bank account using the Offramp APIs.

This guide shows you how to use Bitnob’s Offramp APIs to receive USDT from a user on-chain (e.g., via TRC20) and automatically convert and settle it into Nigerian Naira (NGN) into a local bank account.

We’ll walk through the entire value flow, what each step means, and how to present each step clearly in your application.

## Key concepts

### The quote

Before you receive any crypto, you must **lock in an exchange rate** using a quote. The quote:

* Tells you the exchange rate that will be used (e.g., 1 USDT = ₦1702)
* Defines how many NGN the user will receive
* Is valid for 16 minutes to avoid market volatility

### Source of funds

You're specifying:

* source: "onchain", meaning the user will send USDT from an external wallet
* chain: "TRON", the network the user will use to send the USDT (Tron is fastest and cheapest)

### The address

After the quote is accepted via the initialize step, Bitnob gives you a **USDT address**. This is what you show the user to send funds to.

### Settlement

When funds arrive at the address:

* Bitnob automatically detects the incoming USDT
* Converts it using the locked-in quote rate
* Settles the equivalent NGN into the bank account you provided

### Full flow overview

![Bitnob account setup process](https://mintcdn.com/bitnob/5PGS9OuNC096evyI/images/flow-overview.svg?fit=max\&auto=format\&n=5PGS9OuNC096evyI\&q=85\&s=a2788ab209d55d0e9d130249f36ce0b3)

## Request a quote

Step: Lock in the exchange rate. Full schema: [Create Payouts Quote](https://www.bitnob.dev/api-reference/payouts/create-quote.md).

```bash theme={null}
POST /api/payouts/quotes
```

**Example request:**

```json theme={null}
{
    "from_asset": "USDT",
    "to_currency": "NGN",
    "country": "NG",
    "source": "onchain",
    "chain": "TRON",
    "amount": "1000",
    "reference": "PAYOUT_RS27519160015009872077"
}
```

**Example response:**

```json theme={null}
{
  "success": true,
  "message": "Quote created successfully",
  "data": {
    "payout": {
      "id": "56a44dc1-f8ac-4bf4-93d8-002be684fe82",
      "quote_id": "QT2_21052912",
      "company_id": "00000000-0000-4000-8000-000000000001",
      "status": "QUOTE",
      "source": "ONCHAIN",
      "chain": "CHAIN_TYPE_TRON",
      "from_asset": "USDT",
      "to_currency": "NGN",
      "sat_amount": "1838425",
      "btc_amount": "0.01838425",
      "cent_amount": "139979480",
      "amount": "1000",
      "settlement_amount": "1399794.8",
      "cent_fees": "0",
      "fees": "0",
      "fee_currency": "USD",
      "source_fee": "0",
      "total_amount": "1000",
      "exchange_rate": {
        "rate": "1399.7948",
        "btc_rate": "76210.05",
        "currency": "ngn",
        "effective_rate": "1399.7948"
      },
      "trip": {
        "quote_at": "2026-03-22T12:03:56.314166596Z"
      },
      "reference": "PAYOUT_RS27519160015009872077",
      "country": "NG",
      "expires_at": "2026-03-22T12:19:56.314166596Z",
      "created_at": "2026-03-22T12:03:56.319957313Z",
      "updated_at": "2026-03-22T12:03:56.319957313Z"
    }
  },
  "timestamp": "2026-03-22T12:03:56.327593116Z"
}
```

What this means:

* The rate is guaranteed for 16 minutes, until the expires\_at timestamp
* You must initialize and receive funds within this window

## Initialize the payout

This step binds the quote to the destination bank account: see [Initialize Payout](https://www.bitnob.dev/api-reference/payouts/initialize-payout.md). Confirm the account resolves first with [Account Lookup](https://www.bitnob.dev/api-reference/payouts/account-lookup.md#overview).

```bash theme={null}
POST /api/payouts/:quoteId/initialize
```

**Example request:**

```json theme={null}
{
    "beneficiary": {
        "destination_type": "bank",
        "country": "NG",
        "account_number": "0123456789",
        "bank_code": "000013",
        "account_name": "Ada Okafor"
    },
    "reference": "PAYOUT_RS27519160015009872077",
    "payment_reason": "vendor payment",
    "callback_url": "https://example.com/webhooks/payouts"
}
```

**Example response:**

```json theme={null}
{
  "success": true,
  "message": "Payout initialized successfully",
  "data": {
    "payout": {
      "id": "56a44dc1-f8ac-4bf4-93d8-002be684fe82",
      "quote_id": "QT2_21052912",
      "company_id": "",
      "status": "PENDING_ADDRESS_DEPOSIT",
      "source": "SOURCE_UNSET",
      "chain": "CHAIN_TYPE_TRON",
      "address": "TJZCwdcH2GZRLjt8aAphrhCZtEbJLqEAfC",
      "deposit_amount": "1000",
      "from_asset": "USDT",
      "to_currency": "NGN",
      "sat_amount": "1838425",
      "btc_amount": "0.018384",
      "amount": "1000",
      "settlement_amount": "1399794.8",
      "cent_fees": "0",
      "fees": "0",
      "fee_currency": "USD",
      "source_fee": "0",
      "total_amount": "1000",
      "exchange_rate": {
        "rate": "1399.7948",
        "btc_rate": "76210.05",
        "currency": "",
        "effective_rate": "1399.7948"
      },
      "beneficiary": {
        "account_name": "Ada Okafor",
        "account_number": "0123456789",
        "bank_code": "000013",
        "country": "NG",
        "destination_type": "bank"
      },
      "payment_reason": "vendor payment",
      "reference": "PAYOUT_RS27519160015009872077",
      "country": "",
      "expires_at": "2026-03-22T12:19:56Z",
      "created_at": null,
      "updated_at": null
    },
    "data": null
  },
  "timestamp": "2026-03-22T12:04:33.699062536Z"
}
```

What you should do next:

* Display the returned USDT address to the user
* Prompt them to send the full `deposit_amount` (1000 USDT here) on TRC20 to the address. It is the quoted amount plus fees
* Start tracking the reference and quote\_id for this session

## Funding finalizes the payout

Don't call finalize on an onchain payout. It returns `400` before funding, and the payout finalizes automatically once the user's deposit confirms. The USDT address stays valid until `expires_at`.

## Testing in sandbox

Sandbox has no endpoint that simulates a stablecoin deposit to a payout address, so a payout you don't fund stays `PENDING_ADDRESS_DEPOSIT` until it expires. Test the quote and initialize steps, and the webhook handling with your own signed test payloads.

## Settlement lifecycle (what happens after deposit)

Once the user funds the address, Bitnob handles the rest of the flow: track it with [Get Payout by ID](https://www.bitnob.dev/api-reference/payouts/get-payout.md) or the [payout webhooks](https://www.bitnob.dev/docs/payouts/webhook-events.md#payout-webhooks):

1. It confirms receipt (at least 19 confirmations on Tron, more for larger amounts)
2. Converts the USDT to NGN at the locked quote rate
3. Sends the NGN to the provided bank account
4. Sends a payouts.processing webhook as it starts sending to the settlement rail, then a payouts.withdrawal.success webhook to your callback\_url when completed

## Handling quote expiry, underpayment, or failure

If the user sends less than `deposit_amount`, the deposit is refused, except for a shortfall of up to 0.0001 on stablecoins. Bitcoin has no tolerance. The payout doesn't process and later expires, and the funds stay in your Bitnob balance. Show the exact amount, and tell the user their network fee is on top of it.

If no deposit is received before expiry (16 min), Bitnob cancels the payout and sends a payouts.withdrawal.expired webhook.

If a withdrawal fails (e.g., due to an invalid account), the payout record moves to FAILED and a payouts.withdrawal.failed webhook is sent. You can also poll [Get Payout by ID](https://www.bitnob.dev/api-reference/payouts/get-payout.md) to detect it.

> [!NOTE]
>
> **Always listen for webhooks** to track transaction outcomes.

## Best practices

Always display `deposit_amount` and the chain to the user.

Poll [Get Payout by ID](https://www.bitnob.dev/api-reference/payouts/get-payout.md) if you don't implement [webhooks](https://www.bitnob.dev/docs/payouts/webhook-events.md#payout-webhooks) yet.

Use a unique reference per payout. This is your trace ID.

Store the quote\_id, reference, beneficiary, and address in your database for future reconciliation.

## What this flow enables

You now have a programmable way to:

* Receive USDT from anywhere in the world
* Convert it automatically
* Deliver it in local fiat to a verified bank account
* Handle all messaging via webhooks

### This is ideal for:

* Remittance apps
* Pay suppliers or gig workers in Nigeria
* Crypto liquidation from global wallets

## Next

> [card] [Failure scenarios](https://www.bitnob.dev/docs/stablecoins/error-handling-and-failure-scenarios.md)
>
> Where this flow breaks, and what to do.

> [card] [Reconciliation and ledger integrity](https://www.bitnob.dev/docs/stablecoins/stablecoin-reconciliation-and-ledger-integrity-guide.md)
>
> Keep both ledgers in agreement.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.