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.

Payout Flow

Before you begin

You will need:

what
where to get it
Base URL (live)
https://api.bitnob.com
Base URL (sandbox)
Provided with your account — use it for testing.
Client ID
From your dashboard, under API keys.
Client Secret
Shown once when you generate your API key — store it securely.
Test in sandbox first

Always test end-to-end in sandbox first. Sandbox 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.

Environment

Authentication

Every API request is authenticated with an HMAC-SHA256 signature. You sign each request with your Client Secret and send four headers. Signatures are time-limited, so requests can't be replayed.

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
Required Headers

How to build the signature

Build the payload string by joining four parts with colons, then sign it with your Client Secret using HMAC-SHA256 and hex-encode the result.

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.

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.

Build the Signature

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.
Example Error Responses

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.

Choosing a source

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.

Verify a beneficiary account before paying it — the lookup returns the resolved account name.

Reference Lookups

Step 1 — Create a quote

Locks in the exchange rate and fees for your payout. The response returns a quote_id, the rate, fees, and an expiry. Quotes expire — continue promptly.

field
required
notes
from_asset
Required
The crypto asset funding the payout, e.g. BTC, USDT.
to_currency
Required
Fiat the beneficiary receives, e.g. NGN.
source
Required
offchain (charge balance) or onchain (crypto deposit).
country
Required
Destination country code, e.g. NG.
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.
Create a Quote

Step 2 — Add the beneficiary (initialize)

Attach the recipient's details to the quote. The exact beneficiary fields depend on the country (see Reference Lookups).

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.

Initialize

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.

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

Finalize

Step 4 — Fund the payout

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

onchain — send a crypto deposit to the address / instruction returned from the previous step. You can fund it with BTC (on-chain), Lightning, or stablecoins. Once the deposit confirms, settlement proceeds.

Sandbox tip (onchain)

You can't broadcast a real deposit in sandbox, so simulate it with POST {BASE_URL}/api/payouts/quotes/{quoteId}/deposits and a body of { "amount": "50000" }.

Simulate a Deposit (sandbox)

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

Payout statuses

A payout moves through these states:

status
meaning
pending
Created and awaiting funding.
processing
Funded; 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.

Status Lifecycle

Full worked example (offchain)

The sequence on the right assumes a signed-request helper that sets the X-Auth-* headers for each call. It creates a quote, attaches the beneficiary, finalizes, and tracks by reference — reusing one reference throughout.

Full Worked Example

Webhooks

If you set callback_url when initializing, we send a notification to that URL whenever the payout's status changes. 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.


Error handling

Errors use standard HTTP status codes with a consistent JSON body. Include the request_id from the response when contacting support — it helps us trace the exact request.

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.
Error Response

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.

Did you find this page useful?

Join our Discord