Skip to main content
A webhook is an HTTP request we send to your server when something changes. You do not poll for a deposit; we tell you when it confirms. Webhooks carry the outcome of every asynchronous flow, so they are the record your system should trust. The API response says we accepted an action. The webhook says what happened. This guide covers how to build a handler that survives redelivery, bad payloads, slow downstream services, and missed events.

What an event looks like

Every event carries an event name, an event_id, and a data object holding the reference you supplied. Each product documents its own events:

Deposits and addresses

deposit.success, transfer.success, transfer.failed.

Payouts

payouts.initialized, payouts.processing, payouts.withdrawal.success.

Withdrawals

Withdrawal lifecycle events.

Virtual cards

Card creation, top-up, and transaction events.

Verify every event

Verify the signature before you read the payload. Without this check, anyone who learns your endpoint URL can post a fake deposit to it. We sign each event with HMAC-SHA512 over the request body, keyed by your secret, and send the result in the x-bitnob-signature header. Two rules make the check correct:
  • Sign the raw request body, not a re-serialized object. Re-serializing can change key order or whitespace, and the signature will not match.
  • Compare with a timing-safe function, not ==.
Fail closed. If the signature does not match, discard the request. Do not log the payload as though it were genuine.

Restrict inbound traffic

If your firewall filters inbound traffic, allow our delivery addresses: These addresses can change, and we announce changes in advance.
An IP allowlist is not a security boundary. Any host can send a request from an allowlisted address. Verify the signature on every event, whether or not you filter by IP.

Use HTTPS

Your webhook URL must use https://. Do not accept events over plain HTTP.

Respond fast, process later

Return 200 as soon as the signature is valid. Do the real work afterwards. A handler that writes to a database, calls a third-party service, and sends an email before responding will eventually exceed our timeout. We then treat the delivery as failed and send the event again, even though you processed it.
Any durable queue works: BullMQ or BeeQueue on Node.js, Sidekiq on Ruby, Celery on Python, or Redis Streams and Kafka at higher volume.

Retry behavior

How we read your response: If your own processing fails temporarily, for example your database is down, return a 5xx so the event comes back. Return 400 or 422 only when retrying would never help.
Retries are finite. After the final attempt we stop. An endpoint that is down for an extended period will miss events permanently, so you also need reconciliation.

Make your handler idempotent

Redelivery means your handler will see the same event more than once. Processing it twice sends a second payout or credits a user twice. Check event_id before you act, and record it after. Don’t de-duplicate on reference: several events for one payout share a reference, and some events have none.
Idempotency and references covers the locking needed when several workers run at once.

Store what you receive

Store the event name, the reference, the full payload, the delivery attempt, and the processing result. This table answers most support questions without a call to us. Webhook Retry Behavior

Reconcile what never arrives

Signature verification and idempotency handle the events you receive. Neither helps with an event you never got. Run a scheduled job that lists recent transactions and compares them against your own records. Anything we report as complete and you still hold as pending is a missed delivery. Use Get all transactions for the sweep, and Get transaction by id or reference to resolve a single record.
Do not rely on webhooks alone. Reconciliation is what makes a payments integration correct rather than usually correct.

Test locally

1

Expose your local server

Run a tunnel, such as ngrok http 3000, and set the public URL as your webhook URL in the dashboard.
2

Trigger a real event

In sandbox, simulate a deposit. See Test in sandbox.
3

Force a failure

Return 500 from your handler and watch the retries arrive.
4

Force a duplicate

Process the same event twice and confirm that the second attempt changes nothing.
Use the dashboard webhook log to inspect payloads, delivery status, and retry attempts while you test.

Checklist

  1. Verify the signature on every event, and fail closed.
  2. Return 200 before you process.
  3. Check the event_id before you act.
  4. Store every event you receive.
  5. Reconcile on a schedule.
  6. Serve the endpoint over HTTPS.

Next

Continue to Errors and retries for the status codes and retry rules shared by every endpoint.