The rules below are written so your handler keeps working as new event types and fields arrive - see Events & Payloads for the event catalog.
1. Respond before you process
Return a 2xx immediately, then do the work asynchronously. WalletSuite only acknowledges a delivery on a2xx in time. If you verify the signature, then write to your database, call a third-party API, and then respond, a slow dependency turns into a delivery timeout, which turns into a retry, which turns into a duplicate.
The correct shape: verify the signature, enqueue the event, return 200. Process from the queue.
Express
2. Be idempotent
Dedupe onevent_id with a database unique constraint: the first delivery processes; every later delivery of the same event is a no-op. Delivery is at-least-once, so dedupe on your idempotency key - the event_id (= the Webhook-Id header).
A naive “have I seen this id?” check has a race: two concurrent deliveries both read “not seen,” both process. A conditional insert under a unique constraint closes it - exactly one delivery wins the insert and processes; the rest see the conflict and return.
Postgres
Node + Postgres (pg)
3. Don’t assume order
Useoccurred_at for temporal logic, never arrival order. Delivery order is best-effort, not guaranteed.
Process each event independently. When you need to reason about when something happened on-chain - “is this the most recent transfer?”, “did this arrive before that?” - compare the occurred_at field, not the order packets reached your server.
4. Tolerate the unknown
Write tolerant parsers so new capabilities arrive without a redeploy. The payload versioning contract guarantees additive evolution within your pinnedschema_version, and enums are open. A handler that acknowledges what it doesn’t recognize - new fields, types, and enum values - keeps working as the schema grows.
5. Treat webhooks as signals, not the ledger
Re-query authoritative state before any high-value action. A webhook is a low-latency signal that something happened - it is not your source of truth. Before you credit a large balance, release goods, or trigger a payout, confirm the transfer by reading current state from the REST API. This is defense-in-depth on two fronts:- Against stale data - signature verification proves origin; re-querying proves the transfer is real, final, and still reflected in current state.
- Against gaps - deliveries retry across the full retry window, and a periodic reconciliation sweep that re-queries the API guarantees correctness even if your endpoint is down past it.
6. Store the secret securely
Keepwhsec_… in a secret manager - never in code - and rotate via the API. Treat it exactly like an API key (see Securing Your API Keys). Rotate it if it’s ever exposed - see secret rotation.
Related
Verify Signatures
Full copy-paste verification code, in multiple languages.
Delivery & Retries
The retry ladder, timeouts, and at-least-once semantics behind these rules.
Events & Payloads
Field-by-field reference for the
transfer.received payload.Quickstart
Subscribe, provision a secret, and receive your first event.