Payouts API — Integration Guide

This guide walks you through everything needed to send a payout — from authenticating your first request to confirming that funds have reached your beneficiary.

A payout delivers local fiat (e.g. NGN, KES, GHS) to a beneficiary's bank or mobile-money account, funded from crypto. You create payouts through a simple, quote-based flow:

Authenticate → Create quote → Add beneficiary → Finalize → Fund → Track to completion

Create payout flow

1. Account setup

1.1 Create your account

Visit https://app.bitnob.com/ and create an account with the company name of your choice. Once you're set up, let us know on Slack immediately so the compliance team can enable your account for payouts.

1.2 Get your API credentials

From the home page, open the Settings page and click API Keys. There you'll obtain:

what
notes
Base URL (live)
https://api.bitnob.com
Client ID
Public identifier for your API key.
Client Secret
Shown once when you generate your API key — store it securely.
1.3 Whitelist your IP

For extra security, whitelist your server's IP address before going live. In the dashboard, go to Settings → IP (bottom-left of the vertical navigation), or use this shortcut: http://app.bitnob.com/settings?section=ip&view=endpoints

Requests from non-whitelisted IPs will be rejected once whitelisting is enabled.

Tip

Always test end-to-end in sandbox first — it lets you complete a full payout (including funding) without moving real money. Then switch the base URL to https://api.bitnob.com and your live API keys for production.

Throughout this guide, {BASE_URL} stands for your environment's base URL — https://api.bitnob.com for live, or your sandbox URL for testing.


2. Authentication

All API requests are made against the Bitnob base URL https://api.bitnob.com and authenticated with an HMAC-SHA256 signature. You sign each request with your Client Secret and send four headers. Both your CLIENT_ID and CLIENT_SECRET are required. Signatures are time-limited, so requests can't be replayed.

Full authentication reference

A ready-made signing function is available on the Authentication page.

2.1 Required headers
header
value
X-Auth-Client
Your Client ID
X-Auth-Timestamp
Current Unix time, in seconds
X-Auth-Nonce
A unique random value per request (a UUID works well)
X-Auth-Signature
HMAC-SHA256 signature (hex-encoded) — see below
2.2 How to build the signature
  1. Build the payload string by joining four parts with colons:
Payload

REQUEST_BODY is the exact raw JSON body you send. For requests with no body (e.g. GET, or the finalize call), use an empty string.

  1. Sign it with your Client Secret using HMAC-SHA256 and hex-encode the result:
Signature
Sign byte-for-byte

Sign the body byte-for-byte as it is sent. If you re-serialize or reformat the JSON after signing, the signature will not match.

2.3 Signing example (bash)
Signing example (bash)
2.4 Signing example (Node.js)
Signing example (Node.js)
2.5 Common auth errors
problem
fix
Invalid signature
Confirm the Client Secret and that the signed body exactly matches the sent body.
Timestamp outside acceptable range
Sync your clock; send the timestamp in seconds, not milliseconds.
401 Unauthorized
A header is missing or the signature is wrong.
403 Forbidden
Authenticated, but your key lacks permission for this action.

3. Understanding funding: offchain vs onchain

When you create a quote you choose a source, which decides where the funds come from:

source
meaning
offchain
Your Bitnob account balance is charged directly. No external deposit — the balance must cover the payout.
onchain
You send a crypto deposit to a generated address to fund the payout. The deposit can be BTC (on-chain), Lightning, or stablecoins.

Everything else in the flow is identical; only the funding step differs.


4. Reference lookups (recommended first)

Use these read-only calls to build a valid request. They help you pick a supported country, the exact beneficiary fields it needs, valid bank codes, and transaction limits.

Reference lookups

Verify a beneficiary account before paying it (returns the resolved account name):

Account lookup

For mobile-money destinations, pass type=mobile_money and use the mobile-money provider as the bank_code (e.g. MTN). The account_number is the recipient's phone number in international format.

Example response:

Account lookup — response

Check is_verified is true and confirm account_name is who you expect before continuing. (For mobile money, bank_code / bank_name may come back empty.)


5. The payout flow

Step 1 — Create a quote

Locks in the exchange rate and fees for your payout.

Create a quote

Request:

Create a quote — request (KES)
field
required
notes
from_asset
The crypto asset funding the payout, e.g. BTC, USDT.
to_currency
Fiat the beneficiary receives, e.g. KES, GHS, NGN.
source
offchain (charge balance) or onchain (crypto deposit).
country
Destination country code, e.g. KE.
chain
for onchain
How you'll fund the deposit: lightning, bitcoin, or a stablecoin chain.
settlement_amount
one of
Amount the beneficiary should receive, in to_currency.
amount
one of
Alternatively, the amount in from_asset. Provide one side.
payment_reason
optional
Shown for your records.
reference
optional
Your own unique reference — use it to make retries safe and to look the payout up later.

Response — the created quote is returned under data.payout. Note the status is QUOTE and it carries a short expires_at, so continue promptly.

Create a quote — response (KES)

Key response fields:

field
meaning
quote_id
Use this in the initialize and finalize calls.
status
QUOTE at this stage.
source / chain
Echoed back, e.g. ONCHAIN / CHAIN_TYPE_LIGHTNING.
sat_amount / btc_amount / amount
The crypto you'll deposit (satoshis / BTC).
cent_amount / settlement_amount
What the beneficiary receives (minor units / currency units).
fees / cent_fees
Payout fees.
exchange_rate
The locked rate details for this quote.
expires_at
Quote expiry — finalize before this time.

Another example — Ghana (GHS)

Request:

Create a quote — request (GHS)

Response:

Create a quote — response (GHS)

Step 2 — Add the beneficiary (initialize)

Attach the recipient's details to the quote. The exact beneficiary fields depend on the country (see the reference lookup in Section 4).

Initialize

Request:

Initialize — request

callback_url is where we'll notify your system of status changes. Recommended.

customer_id (optional) links the payout to a customer in your account.

client_meta_data (optional) is a JSON string echoed back to you for your own reconciliation.

Response — the payout moves to status PENDING_ADDRESS_DEPOSIT and, for onchain funding, returns the address to pay. For chain: lightning this is a BOLT11 Lightning invoice (lnbc…); pay it to fund the payout.

Initialize — response

The address field is your funding instruction — pay it in Step 4 before expires_at:

chain
what address contains
lightning
A BOLT11 invoice (lnbc…) — pay it from any Lightning wallet.
bitcoin
A BTC on-chain address — send the exact btc_amount.
stablecoin
The deposit address on the relevant chain.

Another example — Ghana mobile money (GHS)

For mobile money, the beneficiary uses the recipient's phone number as account_number and has no bank_code.

Request:

Initialize — request (Ghana mobile money)

Response:

Initialize — response (Ghana mobile money)

Step 3 — Finalize (approve and submit)

Approves the payout and submits it for processing. No request body — remember to sign with an empty body string.

Finalize

Confirms the payout and submits it for settlement.

For onchain payouts, the funding address was already returned by the initialize step (Step 2). You can pay it as soon as you have it — see Step 4.

For offchain payouts, your account balance is charged at this step.

Finalize response

The finalize response returns the payout object (same shape as Step 2) with the updated status. Field names/values match the initialize response above.

Step 4 — Fund the payout

onchain — pay the address returned by the initialize step (Step 2) before its expires_at. For chain: lightning this is a BOLT11 invoice (lnbc…) you pay from any Lightning wallet; for bitcoin send the exact btc_amount on-chain; for stablecoins send to the provided deposit address. Once the payment is received, settlement to the beneficiary proceeds.

offchain — nothing to do. Your balance was charged at finalize, and the payout proceeds automatically to settlement.

Sandbox tip (onchain)

You can't broadcast a real deposit in sandbox, so simulate it with the request below.

Simulate a deposit (sandbox)

Response:

Simulate a deposit — response

Step 5 — Track to completion

We settle the fiat to the beneficiary and update the payout status. Track it by polling, or (preferred) by receiving webhooks at your callback_url.

Track

Example response (single payout):

Track — response

The list endpoint returns the same objects inside a data.payouts array along with pagination fields (limit, offset, total).


6. Payout statuses

A payout moves through these states (status values are upper-case):

status
meaning
QUOTE
Quote created; not yet initialized.
PENDING_ADDRESS_DEPOSIT
Initialized; awaiting your crypto deposit (pay the address).
PROCESSING
Deposit received; settlement to the beneficiary is in progress.
COMPLETED
Funds delivered to the beneficiary. ✅
FAILED
The payout could not be completed.

Treat COMPLETED as the only success state. Always confirm final status via a status lookup or webhook — don't assume success from the finalize response alone.


7. Full worked example (onchain)

Full worked example (onchain)

8. Webhooks

If you set callback_url when initializing, we send a notification to that URL whenever the payout's status changes.

Example webhook payload (sent to your callback_url):

Webhook payload

The event reflects the new state, e.g. payout.processing, payout.completed, or payout.failed (a failed event includes a reason). Field names above are illustrative — always key your logic off status and your own reference.

Best practices:

Respond quickly with a 2xx. Do heavy processing asynchronously.

Be idempotent — the same event may be delivered more than once.

Use your reference to reconcile events against your own records.

Don't trust a webhook blindly — confirm by calling GET {BASE_URL}/api/payouts/reference/{reference} before releasing goods/services.


9. Error handling

Errors use standard HTTP status codes with a consistent JSON body:

Error response
status
meaning
400
Invalid request — check the message and field details.
401
Authentication failed (missing/invalid signature or headers).
403
Authenticated but not permitted for this action.
429
Rate limited — slow down and retry with backoff.
5xx
Temporary issue on our side — retry with backoff.

Include the request_id from the response when contacting support — it helps us trace the exact request.


10. Go-live checklist

Completed a full sandbox payout end-to-end (completed status observed).

Signature generation verified for both bodied and empty-body requests.

Store your reference for every payout and reconcile via lookups/webhooks.

Handle pending → processing → completed/failed in your system.

Client Secret stored securely (never in client-side code or version control).

Switched Base URL and API keys from sandbox to live.


Support

Include the request_id from the failing response and your payout reference when reaching out. Reach your Bitnob integration contact or the support channel provided with your account.


Share on
Did you find this page useful?

Join our Discord