Search documentation

Search across BlockBee docs, API endpoints, webhooks and guides.

Payout Webhook

View as Markdown

BlockBee notifies your endpoint when a payout reaches a terminal state: confirmed (status=done) or failed (status=error). Use it to reconcile withdrawals: mark a payout complete in your system, or surface the failure reason when one occurs.

You configure the destination URL and method per profile in your BlockBee dashboard payout settings. If no URL is set, BlockBee sends no payout webhook.

Method: You choose in your payout settings:

  • POST (default) - Payload sent in the request body as application/x-www-form-urlencoded.
  • GET - Payload sent as URL query parameters.

Content-Type: The payout webhook is always delivered as form data: application/x-www-form-urlencoded for POST, query-string parameters for GET. There is no JSON option.


How It Works

When a payout settles or fails, BlockBee sends one signed HTTP request to your configured URL and waits for an acknowledgement. Respond with HTTP 200 and you're done. Respond with anything else, or time out, and BlockBee retries with exponential backoff.

Loading diagram...

BlockBee sends exactly one notification per state transition:

  • done - The payout was confirmed on-chain.
  • error - The payout failed. The error field carries the reason.

Expect both states. A payout that fails still produces a webhook, with status=error and error populated. Requests arrive with the User-Agent BlockBee/1.0 (+https://docs.blockbee.io/webhooks).


Configuring the Webhook

Payout webhook settings are configured per profile from your dashboard. You never call an API to set them up.

  1. In your BlockBee dashboard, select the profile you want to configure and open Profile Settings → Payout settings.
  2. Enter your Endpoint URL, a public HTTPS URL. Internal and loopback addresses are rejected, including on redirects.
  3. Pick the Method: POST (default) or GET.
  4. Click Save.

The page prefills with the URL and method you last saved. To turn the webhook off, clear the Endpoint URL and save again. The dashboard asks you to confirm first, since this stops all payout notifications for the profile.

After saving, use the Send test button to validate your endpoint before a real payout depends on it (see Testing your endpoint). Each payout's delivery history is available from its Webhook activity view (see Delivery logs).


Webhook Fields

All values are delivered as strings.

idstringRequired

Payout UUID. Stable across retries and across the done/error states.

Important: Use the id together with status for idempotency. The same payout/status can arrive more than once if a retry fires after you already processed it.

Example:afe11bea-768b-47ae-ba0f-907379fbe5ef

statusstringRequired

Machine-readable status. done when the payout is confirmed, error when it failed.

Example:done

display_statusstringRequired

Human-readable status label, suitable for display.

Example:Done

total_requestedstringRequired

Amount requested, denominated in coin.

Example:0.5

total_requested_fiatstringRequired

total_requested converted to your fiat currency.

Example:32150.00

total_with_feestringRequired

Requested amount plus fee, denominated in coin.

Example:0.5005

total_with_fee_fiatstringRequired

total_with_fee converted to your fiat currency.

Example:32182.15

errorstringRequired

Error text when the payout failed (status=error). Empty otherwise.

blockchain_feestringRequired

Network fee paid to the blockchain, denominated in coin.

Example:0.0005

feestringRequired

BlockBee processing fee, denominated in coin.

Example:0

coinstringRequired

Coin/ticker for the payout (e.g. btc, ltc, trc20_usdt).

Example:btc

timestampstringRequired

Payout timestamp, formatted dd/mm/YYYY HH:MM:SS.

Example:08/06/2026 14:22:01

Example Payload

JSON
{
  "id": "afe11bea-768b-47ae-ba0f-907379fbe5ef",
  "status": "done",
  "display_status": "Done",
  "total_requested": "0.5",
  "total_requested_fiat": "32150.00",
  "total_with_fee": "0.5005",
  "total_with_fee_fiat": "32182.15",
  "error": "",
  "blockchain_fee": "0.0005",
  "fee": "0",
  "coin": "btc",
  "timestamp": "08/06/2026 14:22:01"
}

Security: Verify Webhook Signatures


Acknowledging and Retries

Respond with HTTP 200 to acknowledge the webhook. The payout webhook keys off the status code only; the response body is not inspected. Keep your response small and return it quickly, since slow or oversized responses are treated as failures.

Any non-200 status, timeout, or connection error is treated as a failure, and BlockBee retries with exponential backoff of 6 × 2^(attempt-1) minutes:

text
6m → 12m → 24m → 48m → 96m → 192m → 384m → 768m → 1536m → 3072m

After 11 attempts (~3–4 days), BlockBee stops retrying.


Implementation Examples

The payout webhook delivers form-encoded fields, so read them from the parsed form (POST) or query string (GET), not from a JSON body. Signature verification is delegated to a verifyWebhookSignature helper; see the Verify Webhook Signatures Guide.

// Express.js webhook handler
// Accept both POST (form body) and GET (query string).
app.all('/payout-webhook', express.urlencoded({ extended: true }), (req, res) => {
  // 1. Verify the webhook signature
  if (!verifyWebhookSignature(req)) {
    return res.status(403).send('Invalid signature');
  }

  // 2. Extract the fields
  const fields = req.method === 'POST' ? req.body : req.query;
  const { id, status, error, total_requested, coin } = fields;

  // 3. Ignore test sends (all-zero sentinel id)
  if (id === '00000000-0000-0000-0000-000000000000') {
    return res.status(200).send('ok');
  }

  // 4. Idempotency: de-dupe on (id, status)
  if (isPayoutUpdateProcessed(id, status)) {
    return res.status(200).send('ok');
  }

  // 5. Handle both terminal states
  if (status === 'done') {
    markPayoutComplete({ id, amount: total_requested, coin });
  } else if (status === 'error') {
    markPayoutFailed({ id, reason: error });
  }

  // 6. Record that this (id, status) was processed
  markPayoutUpdateProcessed(id, status);

  // MUST return 200 to acknowledge
  res.status(200).send('ok');
});

Key Checks

  1. Verify the signature - Confirm x-ca-signature against the raw bytes before acting.
  2. Recognize test sends - Treat the all-zero id as a no-op (see Testing your endpoint).
  3. De-dupe on (id, status) - The same terminal update can arrive more than once.
  4. Handle both states - Process done and error; the failure reason is in error.

Testing Your Endpoint

Once a webhook URL is saved, a Send test button appears on the payout settings page. It fires a single, synchronous webhook at your configured URL so you can validate your endpoint, including signature verification, before any real payout depends on it. The panel previews the exact payload it will send and reports the result inline.

The test request is identical to a real one: same method (GET/POST), same x-ca-signature signing, same payload shape, with two differences:

  • It is a one-off probe. It creates no payout record, writes no delivery log, and is never retried.
  • The payload carries a sentinel id of all zeros so you can recognize a test and avoid acting on it.
JSON
{
  "id": "00000000-0000-0000-0000-000000000000",
  "status": "done",
  "display_status": "Done",
  "total_requested": "1",
  "total_requested_fiat": "50.00",
  "total_with_fee": "1.001",
  "total_with_fee_fiat": "50.05",
  "error": "",
  "blockchain_fee": "0.001",
  "fee": "0",
  "coin": "btc",
  "timestamp": "08/06/2026 14:22:01"
}

The button reports the result back: HTTP 200 means your endpoint accepted the test; any other value (including a transport failure such as a timeout or blocked connection) means it didn't. Tests are rate-limited to one per minute and protected by a CAPTCHA.


Delivery Logs

BlockBee records every delivery attempt for each payout. In your dashboard, open any payout and select Webhook activity (next to Payout information) to review its delivery history: the URL each attempt was sent to, the response received, the resulting status, and when it was sent. Use it to confirm a payout webhook was delivered, or to debug one that wasn't.


Reliability and Best Practices

  • Verify signatures. Always check x-ca-signature against the raw request before trusting the payload.
  • Be idempotent. De-dupe on (id, status); a retry can fire after you already processed the update but the 200 didn't reach BlockBee.
  • Return 200 fast. Do heavy processing asynchronously. The connection has a short timeout, and over-large or slow responses count as failures.
  • Expect both states. You receive a webhook for the terminal error state too, with error populated.
  • Use a public, SSRF-safe URL. Internal and loopback addresses are rejected, including on redirects.

For BlockBee's general webhook delivery, retry, and timeout behavior, see the How Webhooks Overview.