Version: v4
Last Updated: 2026-07-15
Overview
When a Claim Link reaches a terminal state and the corresponding on-chain settlement succeeds, AllScale POSTs a notification to your configured webhook endpoint. This lets your backend react to a claim, an expiry/refund, or a sender cancellation/refund without polling. There are exactly three terminal events:⚠️The claim webhook reuses the same HMAC-SHA256 request-signing scheme as every other AllScale webhook — see Webhook Callback Signing & Payload Guide for the canonical-string construction, header definitions, and verification flow. This document covers only what is specific to the Claim Link webhook: which links emit it, the payload shape, and the delivery/retry behavior.cancelledis new. Earlier versions of this document stated that there is nocancelledevent — that is no longer true. Sender cancel / claim-back is live (see the Cancel (Claim-Back) API), and its refund settles through the same path as an expiry refund, so it emits the same payload shape withrefund_tx_hashset. If your handler was written against the old two-event contract, it must now tolerate a thirdeventvalue — do not treat an unknown event as an error. The callback fires after on-chain settlement, not on the status change alone. A link whose status becomes expired or cancelled does not emit its event until the refund succeeds — if a refund is still pending or has failed, no callback is sent (yet). Do not treat “status = cancelled” as equivalent to “acancelledwebhook will arrive”; reconcile via the Claim Link status API for the authoritative state.
Which links emit a webhook
A callback is sent only when a webhook URL is configured for the link:
Exactly one of
batch_id / store_id is populated in each payload,
identifying how the link was created. The store-level
claim_link_webhook_url must be an https:// URL (plain http://
is accepted only in the sandbox environment).
The callback is signed with the same store credentials (cancelledonly ever carriesstore_id. A batch sub-link cannot be cancelled individually (its funds are pooled and returned through the batch’s aggregate refund flow), so acancelledevent is never emitted for a batch link —batch_idis alwaysnullon acancelledpayload.
api_key + api_secret) that authenticated the create request — i.e.
the store that owns the batch or the single-create link.
Webhook Request
Method
Content-Type
Required Headers
Identical to all AllScale webhooks (see the signing guide):
The signature is computed over the canonical string
allscale:webhook:v1\nPOST\n<path>\n<query>\n<webhook_id>\n<timestamp>\n<nonce>\n<sha256_hex(body)>,
signed with your api_secret. Always verify the signature against the
raw request-body bytes before parsing the JSON.
Webhook Payload
The request body is exactly the following JSON structure. All money values are decimal strings in token units (never floats).JSON Field Definitions
Null keys are always present. Fields that do not apply to a given event/link type are serialized asnull(not omitted). Your parser should toleratenullfor every optional field above. The HMAC covers the exact bytes sent, so do not re-serialize before verifying.
Example — claimed (merchant single-create link)
Example — expired (batch-created link)
Example — cancelled (merchant single-create link)
Emitted after you called the
Cancel (Claim-Back) API
and the refund settled on-chain. Note batch_id is always null here,
and refund_tx_hash is set exactly as for expired.
Idempotency & Dedupe
webhook_id is stable and deterministic for a given
(claim_link_id, event) pair — every retry and every at-least-once
re-delivery of the same event carries the same webhook_id.
- Treat
webhook_idas the dedupe key: record processed IDs and ignore repeats. - Because delivery is at-least-once, you will occasionally receive the same event more than once (e.g. after a retry or a catch-up sweep). Idempotent handling is required.
Delivery & Retry Behavior
- At-least-once, best-effort. The first delivery attempt is
synchronous; on any non-
200response (or timeout / connection error) AllScale schedules up to 35 exponential-backoff retries. - Retry window. Backoff grows (~5s, 10s, 20s, 40s, 80s) then caps at 120s per attempt, giving a real delivery window of roughly 30 minutes (with jitter) up to ~1 hour — enough to survive a brief outage or deploy on your side.
- No replay after exhaustion. Once the retries are exhausted the job is dead-lettered and not re-delivered by any drain/replay mechanism. If your endpoint is down for longer than the retry window, you will miss that callback — reconcile via the Claim Link status API rather than relying solely on the webhook.
- Missed-
expiredcatch-up. For batch links, AllScale runs a catch-up sweep that re-enqueuesexpiredwebhooks that were missed within the last 7 days. This is an additional at-least-once path (hence the dedupe requirement above); it does not cover theclaimedorcancelledevents, nor store single-create links. Becausecancelledis store single-create only, it has no catch-up path — if your endpoint is down past the retry window you will miss it, so reconcile rather than relying on the callback alone. - Request timeout. Each attempt uses a 5-second timeout; respond
quickly (return
200and process asynchronously if needed).
Response Expectations
Your endpoint should return:200 OK— the callback is considered delivered; no retry.- Any non-
200— treated as a failure; the callback is retried per the policy above.
Best Practices
✅ VerifyX-Webhook-Signature against the raw body bytes before
processing✅ Dedupe on
webhook_id — you will receive duplicates✅ Branch on
event (claimed / expired / cancelled) and read the
matching *_tx_hash (claim_tx_hash for claimed, refund_tx_hash for
expired and cancelled)✅ Treat an unrecognized
event as a no-op you ack with 200, not as
an error — new terminal events may be added (as cancelled was)✅ Parse
amount with a Decimal type, not a float✅ Return
200 fast; do heavy work asynchronously✅ Reconcile against the Claim Link status API for anything longer than the ~1h retry window
❌ Never log
api_secret or signatures
End of Document