Webhooks push purchase, buyback, deposit and redemption events to your backend so you don't
have to poll. Manage them with the register, list, delete and delivery log endpoints
(scope webhooks:manage).
POST /api/v1/webhooks with { url, event_types? } — omit event_types to subscribe to everything. Up to 20 webhooks per
partner. URLs that resolve to private or internal addresses are rejected (SSRF guard).
The response includes the HMAC signing_secret — a 64-hex string that is shown exactly once, at creation. Store it immediately; GET /webhooks never returns secrets.
Each event is delivered as a POST to your URL with these headers:
X-Mystery-Event: purchase.fulfilled X-Mystery-Delivery: <event_id> # idempotency key — dedupe on this X-Mystery-Timestamp: <unix_ms> X-Mystery-Signature: t=<unix_ms>,sha256=<hex>
And this body shape:
{ "event": "purchase.fulfilled", "id": "purchase.fulfilled:5012",
"data": { "purchase_id": 5012, "status": "FULFILLED", "onchain_request_id": "0xabc…" } } HMAC_SHA256(signing_secret, "<timestamp>.<raw_body>") using the raw request body.sha256= value in X-Mystery-Signature.X-Mystery-Delivery — retries re-send the same id.Any non-2xx response (or timeout) is retried up to 6 attempts with exponential backoff. Use the delivery log to debug a consumer that is not receiving events.
| Event | Fires when |
|---|---|
deposit.credited | A USDC deposit landed and was credited to an end-user balance |
purchase.reserved | Custodial purchase accepted — credits held |
purchase.submitted | On-chain purchase broadcast |
purchase.fulfilled | VRF revealed the items — purchase complete |
purchase.refunded | Purchase refunded (e.g. machine empty) — hold returned |
purchase.failed | Pre-broadcast failure — hold returned |
buyback.confirmed | The holder accepted your buyback offer on-chain |
buyback.transfer_held | Bought-back NFT is waiting because no pool wallet is set (sent once) |
buyback.card_transferred | Bought-back NFT delivered to your pool wallet |
buyback.transfer_failed | Terminal transfer failure after max retry attempts |
redemption.prepared | Redemption prepared — burn calldata issued (emitted inline by prepare) |
redemption.updated | Every redemption status transition thereafter (burn, fulfillment, completion, failure) |
The buyback-transfer and redemption payloads carry everything needed to reconcile state —
for example buyback.card_transferred includes the token_id, contract_address, to_address and tx_hash of the NFT delivery, and buyback.transfer_held tells you to
set a pool wallet via PUT /mystery/pool-wallet.