Tutorial: Accept USDT from Users and Settle in Naira

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 you can provide a seamless user experience 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 15 minutes to avoid market volatility

Source of Funds

You're specifying:

source: "onchain" – meaning the user will send USDT from an external wallet

chain: "trc20" – which network the user will use to send the USDT (TRC20 is fastest and cheapest)

The Address

After the quote is accepted via the initiate 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
1

Request a Quote

Step: Lock in the exchange rate.

Endpoint

Example Request

json
Note

The amount is in USDT1000 means 1000.00 USDT.

Example Response

json

What this means:

The rate is guaranteed for 15 minutes, until the expires_at timestamp

You must initialize, finalize, and receive funds within this window


2

Initiate the Payout

This step binds the quote to the destination bank account.

Endpoint

Example Request

json

Example Response

json

What you should do next:

Display the returned USDT address to the user

Prompt them to send exactly 1000 USDT on TRC20 to the address

Start tracking the reference and quote_id for this session


3

Finalize the Payout

This approves the payout and submits it for processing. It takes no request body — remember to sign with an empty body string.

Required before expiry

You must call finalize before the expires_at returned by initialize. If it passes, the quote is dead — re-run the quote and initialize steps to lock a fresh rate.

Endpoint

Example Response

json

The payout moves to PENDING. The USDT address stays valid until expires_at — once the deposit lands, settlement proceeds automatically.


4

Simulate Deposit (Sandbox Only)

For testing purposes, you can simulate a deposit.

Simulate Deposit

Example Request

Simulate Deposit Body
Sandbox-only helper

This endpoint exists only in sandbox — it tells the simulator to act as if the user just deposited the specified amount of currency at address on chain. The resulting transaction shows up in the Transactions API with metadata.is_simulated: "true" and a sim_* reference. Production accounts will reject the call.


5

Settlement Lifecycle (What Happens After Deposit)

Once the user funds the address, Bitnob handles the rest of the flow:

1

It confirms receipt (usually within 1–2 blocks for TRC20)

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 when the settlement rail picks up the payout, then a payouts.withdrawal.success webhook to your callback_url when completed


6

Handling Quote Expiry, Underpayment, or Failure

If the user sends less than quoted, the payout still processes — the fiat amount delivered is pro-rated based on the amount actually received, with the difference calculated at the original quoted FX rate (within allowed margin).

If no deposit is received before expiry (15 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 — poll GET /api/payouts/:id to detect it.

Note

Always listen for webhooks to track transaction outcomes.


Best Practices

Always display the exact amount and chain to the user.

Poll the GET /api/payouts/:id endpoint if you don't implement 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


Share on
Did you find this page useful?

Join our Discord