Skip to main content
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 a 2xx 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 on event_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

Use occurred_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 pinned schema_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

Keep whsec_… 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.

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.