Skip to content
Developers/Webhooks

Webhooks Guide

Receive real-time batch events from Disbursed with HMAC-signed POSTs to your HTTPS endpoint. Five-attempt retries, replay-resistant signatures, no polling required.

Last updated May 15, 2026

Webhooks let your backend react to batch state changes the moment they happen on BSC — no polling, no GraphQL subscriptions, and no chain indexers to maintain yourself. Disbursed POSTs a JSON event to an HTTPS URL you control whenever something interesting happens to one of your batches.

Quickstart

  1. Go to Dashboard → Webhooks and create an endpoint. Save the signing secret — it is only shown once.
  2. Subscribe the endpoint to the events you care about (most teams pick batch.confirmed and batch.failed).
  3. Implement the verifier in your language of choice using the recipe below.
  4. Click Send test event in the dashboard to deliver a signed batch.confirmed and confirm your handler returns 2xx promptly.

Events

Three batch lifecycle events ship today. Payloads are UTF-8 JSON and the envelope is identical for every type so you can branch on event.type.

EventFired whendata contains
batch.createdA new batch has been recorded and is awaiting on-chain confirmation.batch_id, tx_hash, token_address, token_symbol, total_amount (wei), recipient_count
batch.confirmedThe on-chain transaction succeeded — funds have moved.batch_id, tx_hash, block_number, gas_used
batch.failedThe on-chain transaction reverted or never confirmed within the watch window.batch_id, tx_hash, reason ("revert" | "timeout")

Request format

JSON, UTF-8. The top-level envelope is shared by every event:

Event preview

Field guide

Hover/long-press highlighted keys inside your editor while iterating — this rail mirrors JSON fields for rapid lookup.

id
Unique UUIDv7 (evt_*); treat as canonical idempotency key.
type
Emitted after the indexer observes a mined success receipt.
created_at
Timestamp when Disbursed marked the watcher success.
batch_id
Batch identifier surfaced across exports + dashboards.
tx_hash
BSC consensus hash for the disbursement txn.
block_number
Block carrying the disbursement txn.
gas_used
Actual gas burned for execution.

Headers Disbursed mirrors key metadata on the wire for easier logging and correlation.

HeaderValue
X-Disbursed-Signaturet=<unix>,v1=<hex> — HMAC-SHA256 over "<timestamp>.<raw body>"
X-Disbursed-TimestampSame unix timestamp (seconds) referenced in the signature header.
X-Disbursed-EventThe event type; identical to event.type in the JSON body.
X-Disbursed-DeliveryUnique delivery id (whd_…). Use for support tickets and log correlation.
X-Disbursed-Attempt1-based attempt counter (1 on the first try, up to 5 on retries).
User-AgentDisbursed-Webhooks/1.0

Verifying the signature

Every request is signed with HMAC-SHA256 using your endpoint's secret. The signed material is the literal string <timestamp>.<raw-body> — note the dot delimiter. The timestamp matches X-Disbursed-Timestamp and the t= parameter inside X-Disbursed-Signature.

Verification recipe

  1. Parse X-Disbursed-Signature as t=<ts>,v1=<hex>.
  2. Reject the request if |now − ts| exceeds five minutes (replay protection).
  3. Compute HMAC_SHA256(secret, ts + "." + raw_body).hexdigest().
  4. Compare the digest to v1 with a constant-time helper (crypto.timingSafeEqual, hmac.compare_digest, subtle.ConstantTimeCompare, etc.).

Code samples

Select language tab
server.js

Each sample keeps the raw payload bytes intact, rejects stale timestamps, compares digests in constant time, parses JSON only after verification, and returns 2xx before doing slow work asynchronously.

Retries and failure policy

Successful delivery requires any 2xx response within ten seconds. Everything else — 4xx, 5xx, network failure, TLS problems, or exceeding the timeout — counts as failure and schedules a retry.

Attempts

Disbursed retries up to five times per delivery id before marking the event as failed.

Backoff

Scheduling after each failure: ≈30 seconds → 2 minutes → 10 minutes → 60 minutes between attempts.

Timeout

Every attempt must receive a 2xx acknowledgement within 10 seconds or it is treated as a failure.

Auto-pause

Ten consecutive endpoint failures flip the endpoint status to failed—fix the handler, then re-enable in the dashboard.

After each attempt you can inspect the stored body, response code, and timing under Dashboard → Webhooks → Deliveries.

Idempotency and ordering

Disbursed guarantees at-least-once delivery rather than exactly-once. Short network blips and handler timeouts can both cause the exact same logical event (same event.id) to arrive twice. Persist the id the first time you observe it (evt_* prefixes a UUIDv7) and safely ignore duplicates thereafter.

Because deliveries can be replayed out of arrival order, treat created_at (ISO-8601 UTC emitted by Disbursed) as the canonical ordering signal when collapsing state transitions.

Security checklist

  • Serve production webhooks strictly over HTTPS—plain HTTP and private addresses fail validation before dispatch.
  • Verify the Disbursed HMAC on every inbound request before interpreting `event` fields.
  • Reject timestamps outside of a ±5‑minute freshness window so captured deliveries cannot be replayed forever.
  • Compare digests using constant‑time primitives (`timingSafeEqual`, `hash_equals`, `subtle.ConstantTimeCompare`, …).
  • Rotate webhook secrets promptly if exposure is suspected—recreating an endpoint issues a fresh key instantly.
  • Treat payloads as hints: reconcile high-impact business logic against on-chain confirmations via `tx_hash` when warranted.
  • Keep listener routes dedicated and hard to guess; avoid multiplexing webhook traffic inside generic POST handlers.

Testing locally

bash
ngrok http 3000

Forward the HTTPS URL shown in ngrok or cloudflared into the dashboard webhook form, then confirm deliveries land on your machine with valid signatures before shipping to staging.

Community

Questions, clarifications, or suspected doc bugs belong in GitHub (open an issue). The webhook sender lives in-repo at api/src/lib/webhooks.ts if you prefer reading implementation details directly.