Try It Out

Webhooks

Learn how to listen for transactions that happen on your account

A webhook is a URL on your server where we send payloads for transaction events. For example, if you implement webhooks, we will immediately notify your server with a btc.lightning.received.success event Once a lightning payment is received. Whenever you receive a webhook notification from us, return a 200 OK to avoid resending the same event again from our server.


Try It Out

Verifying Events

Verifying that these events come from Bitnob is necessary to avoid creating transactions due to a fraudulent event.

To verify events, validate the x-bitnob-signature header sent with the event. The HMAC SHA512 signature is the event payload signed with your secret key.

Notification Retries

When posting notifications, we expect to receive a 200 response code from you. If the response code is not 200, we retry sending the event 3 times after the first failure.

This way, whenever you experience downtime on your end, your updates will still be sent.

Don't rely on webhooks entirely

Note

We recommend that you set up a service to always query transactions, in the event that webhooks keep failing.

Testing Webhooks

Since notifications must always be available on a publicly accessible URL, you are likely to run into issues while starting to build your application in a local environment. You can easily get around this by using a tool like ngrok or localtunnel

Create a tunnel, and update the new webhook URL setting on your dashboard. Only do this in your test environment to avoid leaking data to the public.

Verify Bitnob Webhook (Node.js - Express)

Try It Out

Source IPs

If your firewall restricts inbound traffic, allow requests from the addresses below so our events can reach your endpoint.

ip address
use
18.171.51.218
Webhook delivery
13.134.154.133
Webhook delivery

These addresses can change. We announce changes ahead of time, but an allowlist is not a substitute for verifying the x-bitnob-signature header — signature verification is the check that stays correct even if our infrastructure moves.

Don't allowlist by IP alone

Any host can send a request from an allowlisted address. Always validate the signature on every event, whether or not you filter by IP.

Allow Bitnob webhook IPs (nginx)

Try It Out

Payout Webhooks

Payout webhooks track a payout created through the payouts API (quote → initialize → finalize) as it moves toward settlement. Every event carries the payout record itself, so the field names match the payouts API response.

Payout Initialized

Fired when a payout has been initialized against a quote and accepted for processing. Settlement has not happened yet — wait for payouts.withdrawal.success before treating the funds as delivered.

Note

data.status on payout webhooks is the event outcome, lowercase — initiated, processing, success, expired. It is not the payout record's lifecycle status returned by Get Payout, which is uppercase and uses a different vocabulary (QUOTEINITIATEDPENDINGPROCESSINGCOMPLETED / FAILED). Check the event field to decide how to handle each webhook — not data.status.

1
eventString

Webhook event name. 'payouts.initialized' indicates the payout was accepted for processing.

2
event_idUUID

Unique ID for this webhook delivery. Use it to de-duplicate events on retries.

3
data.idUUID

The payout's internal identifier — the same id returned by Initialize / Finalize and accepted by GET /api/payouts/:id.

4
data.quote_idString

The quote this payout was created from, e.g. 'QT2_21434949'.

5
data.company_idUUID

The UUID of the company that owns the payout.

6
data.referenceString

Your reference for the payout, echoed back for reconciliation.

7
data.statusString

Outcome carried by this event. 'initiated' on payouts.initialized.

8
data.from_assetString

The crypto asset debited, lowercase — e.g. 'btc', 'usdt'.

9
data.to_currencyString

The fiat currency to be paid out, lowercase — e.g. 'ngn'.

10
data.amountString

Amount debited in from_asset.

11
data.settlement_amountString

Amount to be credited to the beneficiary in to_currency.

12
data.feesString

Fee charged for processing the payout, in from_asset.

13
data.beneficiaryObject

The destination the funds are headed to. Contains country, bank_code and a masked account_number (e.g. '****7654').

14
data.created_atTimestamp

When the payout record was created (ISO 8601, UTC).

15
data.updated_atTimestamp

When the payout record was last updated (ISO 8601, UTC).

payouts.initialized

Payout Processing

Fired when the payout has been picked up by the settlement rail and is on its way to the beneficiary. This is an in-flight update, not a terminal state — wait for payouts.withdrawal.success before treating the funds as delivered.

Same fields as Payout Initialized

The payload carries the same fields as Payout Initialized. Refer there for the per-field explanations. One difference:

1
data.statusString

'processing' on this event.

payouts.processing

Payout Withdrawal Successful

Fired when the payout has settled to the beneficiary. This is the event to act on when crediting an order or marking a transfer complete.

Same fields as Payout Initialized

The payload carries the same fields as Payout Initialized. Refer there for the per-field explanations. Two differences:

1
data.statusString

'success' on this event, rather than 'initiated'.

2
data.completed_atTimestamp

When the payout reached its terminal state — here, when settlement completed (ISO 8601, UTC). Absent while the payout is still in flight.

payouts.withdrawal.success

Payout Withdrawal Expired

Fired when a payout was not completed before its quote expired. No funds were settled to the beneficiary — to retry, create a fresh quote and initialize again.

Same fields as Payout Initialized

The payload carries the same fields as Payout Initialized. Refer there for the per-field explanations. Differences:

1
data.statusString

'expired' on this event.

2
data.completed_atTimestamp

When the payout reached its terminal state — here, when it expired (ISO 8601, UTC).

3
data.payment_reasonString

The payment_reason you supplied on Initialize, echoed back — e.g. 'Crypto offramp'. Present when one was set.

payouts.withdrawal.expired
Did you find this page useful?

Join our Discord