Skip to content

Webhooks (events) ​

When a deposit is credited or a withdrawal changes state, we POST an event to your webhook URL.

Event format ​

json
{
  "event_id": "evt_1987654321098754",
  "type": "deposit.confirmed",
  "created_at": "1762650000000",
  "data": { "deposit_id": "1987654321098752", "status": "CONFIRMED", "amount": "12.5", "...": "..." }
}

data is identical to the response of the matching GET endpoint, as a snapshot at event time:

typeWhendata equals
deposit.confirmedThe deposit reached the required confirmations and was creditedGET /v1/deposits/{deposit_id}
withdraw.broadcastedThe withdrawal transaction was broadcast and awaits confirmationGET /v1/withdraws/{withdraw_id}
withdraw.confirmedThe withdrawal is confirmed (final); in-pool transfers go straight heresame
withdraw.failedThe withdrawal failed (final); see data.reason; for pooled merchants the amount frozen at creation is releasedsame
withdraw.rejectedThe withdrawal was rejected in review (final); see data.reason; for pooled merchants the amount frozen at creation is releasedsame

New event types may be added — return 2xx and ignore types you don't recognise.

Headers and signature ​

HeaderDescription
X-Webhook-IdEquals event_id
X-TimestampTime of this delivery attempt, Unix milliseconds (changes on every retry)
X-Signaturehex(HMAC-SHA256(webhook secret, X-Timestamp + "\n" + body))

Verify against the raw body bytes you received — don't parse and re-serialize the JSON first. Also reject timestamps more than 5 minutes from your clock to prevent replays.

Test vector: webhook secret whsec-test, X-Timestamp: 1762650000000, body

json
{"event_id":"evt_1","type":"deposit.confirmed","created_at":"1762650000000","data":{}}

Expected signature: 31c194e933a5f62c752bace322b571f8e281edf589c97c5b672ac245e8a54664. See Code examples for implementations.

Acknowledgement, retries and deduplication ​

  • Any 2xx response counts as success; the body is ignored. Each attempt times out after 30 seconds.
  • On failure (non-2xx, timeout, connection error) each round retries immediately up to 3 times, then backs off until the next round — 1 s, 3 s, 10 s, 30 s, 2 min, 5 min, 10 min, 30 min, 2 h, 6 h, then every 12 h. After 15 rounds (about 3 days) the event is marked FAILED and delivery stops.
  • The same event can arrive more than once (retries, manual resends). Dedupe by event_id and keep your handler idempotent.
  • Events can arrive out of order. Trust data.status; query the GET endpoint for the latest state when needed.
  • Return 2xx quickly and do slow work asynchronously.

Recovering missed events ​

  • GET /v1/events?delivery_status=FAILED lists undelivered events — process their data;
  • POST /v1/events/{event_id}/resend redelivers immediately to your current webhook URL (same event_id);
  • if a deposit has neither a webhook nor a record (e.g. the chain feed missed it), rescan it by transaction hash with POST /v1/deposits/rescan.