Webhooks
Webhooks let you skip polling and react to state changes the moment they happen. Subscribe a URL, get HTTPS POSTs whenever any resource transitions state.
Quick start
- List the event catalog —
GET /v2/webhook-event-types. Every event we can fire, with adetectionmode telling you how reliably it lands today. - Create a subscription —
POST /v2/webhook-subscriptionswith{ url, enabled_events: ["pool.live", "airdrop.claimed"] }or["*"]for everything. The response includes a one-timesecret— store it now, you can't read it again. - Verify the signature on every incoming POST (see below) and process the
data.objectpayload.
Subscription lifecycle
POST /v2/webhook-subscriptions → returns the subscription + full secret (once)
GET /v2/webhook-subscriptions → list yours (paginated)
GET /v2/webhook-subscriptions/{id} → single read (secret_preview only)
PATCH /v2/webhook-subscriptions/{id} → toggle active, change URL, change enabled_events
DELETE /v2/webhook-subscriptions/{id} → remove
Delivery payload
Every webhook POST has this shape:
{
"id": "evt_a1b2c3d4e5f6",
"type": "pool.live",
"api_version": "2.0",
"created": 1748800000,
"data": {
"object": {
"resource_type": "pool",
"resource_id": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"type": "dbc",
"base_mint": "..."
}
},
"external_uid": "campaign_2026_q2",
"metadata": { "ledger_id": "tx_abc123" }
}external_uid and metadata echo whatever you passed on the original write that produced the resource — that's how you reconcile back to your own system.
Signature verification
Every delivery includes header X-Pirate-Signature: t=<unix_ts>,v1=<hex>. Verify with:
import { createHmac, timingSafeEqual } from "crypto";
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Reject if (a) the timestamp is older than 5 minutes (replay protection) or (b) the signature doesn't match.
Retry policy
When delivery is live, we'll retry on any non-2xx response or timeout (>10s):
| Attempt | Delay after previous |
|---|---|
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
| 7 (final) | 24 hours |
After 7 failures the subscription is auto-active: false'd. Re-enable via PATCH /v2/webhook-subscriptions/{id}.
Detection modes (read this)
Not every event is real-time. Each event in GET /v2/webhook-event-types exposes a detection field:
synchronous— fires immediately from the route handler. Live today.tx_confirmed— fires after the user-signed transaction finalizes on-chain. Requires the transaction watcher to be running; it's part of the v2 rollout but separate from the API process.polling_required— needs background polling (e.g., comparingquoteReserveto migration threshold every N seconds). Not yet shipped; events with this detection mode will be emitted once the poller comes online.
Subscribing to events whose detection mode hasn't shipped yet is harmless — you just won't receive them until that piece of infrastructure lands.
Current status
The subscription CRUD endpoints are live. The delivery worker (the process that reads events and fans out HTTPS POSTs) is the next milestone — events ARE being persisted to the events table today and will be replayed once the worker ships.