Skip to main content
AllScale Open API
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:
⚠️ cancelled is new. Earlier versions of this document stated that there is no cancelled event — 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 with refund_tx_hash set. If your handler was written against the old two-event contract, it must now tolerate a third event value — 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 “a cancelled webhook will arrive”; reconcile via the Claim Link status API for the authoritative state.
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.
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).
cancelled only ever carries store_id. A batch sub-link cannot be cancelled individually (its funds are pooled and returned through the batch’s aggregate refund flow), so a cancelled event is never emitted for a batch link — batch_id is always null on a cancelled payload.
The callback is signed with the same store credentials (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 as null (not omitted). Your parser should tolerate null for every optional field above. The HMAC covers the exact bytes sent, so do not re-serialize before verifying.
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_id as 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-200 response (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-expired catch-up. For batch links, AllScale runs a catch-up sweep that re-enqueues expired webhooks 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 the claimed or cancelled events, nor store single-create links. Because cancelled is 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 200 and 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

✅ Verify X-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