Webhooks
Signed events for launches, payouts, low credit and keys at their limit, as they happen.
Add an endpoint (a public https:// URL) on the dashboard's Webhooks page or with POST /webhooks, for some or all events. The answer carries its signing secret (whsec_...): store it, as only a retry with the same Idempotency-Key shows it again. A project has up to 5 endpoints.
Events
| Event | When | data holds |
|---|---|---|
launch.confirmed |
A launch the chain confirmed: its token is on its route. | launch |
launch.failed |
A launch the chain refused, or whose route came out other than prepared. | launch |
token.imported |
An import the chain confirmed: the existing token is on its route. | launch |
token.graduated |
A token's curve completed: it trades on PumpSwap, its fees still on its route. | token, graduated_at |
payout.credited |
A payout of a token's creator fees became AI credit. | payout |
account.low_balance |
An account's spendable credit fell under the threshold: once, until a credit lifts it back. | account, threshold_usd |
key.limit_reached |
A router key's call was refused for its spending limit: once in each of the limit's periods. | key |
route.changed |
A token's creator fees no longer follow the route Fuel locked (a takeover on pump.fun): its payouts stop. | token, reason, detected_at |
POST /webhooks/{id}/test sends a ping to one endpoint. Every delivery is a POST with the same envelope. data holds what the event is about, in the API's own shapes:
{
"id": "evt_4c9LGfliJda80U3V",
"type": "payout.credited",
"created_at": "2026-10-07T13:40:01.000Z",
"project": "prj_joucOmKkV9Ikdf92",
"data": {
"payout": {
"token": "MASi45ub7Qe4ZE36UT5G6cU4ud8Fhhe4deS4F3cw9KTA",
"account": "acc_arqp1qhbpvjhzifE",
"signature": "dYmM6J4tmCUz5J2h...",
"event": "2.3",
"slot": "372918466",
"quote_mint": "So11111111111111111111111111111111111111112",
"distributed_lamports": "61500000",
"ai_bps": 5600,
"ai_lamports": "34440000",
"sol_usd": { "price": "221.04", "confidence": "0.064", "published_at": "2026-10-07T13:39:58.000Z" },
"credited_usd": "7.610413",
"created_at": "2026-10-07T13:40:01.000Z"
}
}
}Its headers name the event too: fuel-event-id, fuel-event-type, and the signature.
Checking a signature
Each delivery carries fuel-signature: t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of <t>.<raw body> under the endpoint's secret. Recompute it over the raw body, compare in constant time, and refuse a t more than 5 minutes from your clock.
import { verifyWebhook } from "@fuelpad/sdk/webhooks";
const event = await verifyWebhook({
body: await request.text(),
header: request.headers.get("fuel-signature"),
// Or [current, previous] while you rotate it.
secret: process.env.FUEL_WEBHOOK_SECRET!,
});
// event is typed: switch on event.type to read event.data.Read the body as raw text before parsing it: parsing and serializing again changes the bytes. After POST /webhooks/{id}/rotate, deliveries carry both secrets' signatures for a day.
Delivery
- Answer 2xx within 10 seconds. Anything else, a redirect included, is a failure.
- Failures are retried with backoff (1, 5, 15 and 30 minutes, then hourly and longer) for 24 hours.
- An event can arrive more than once: use its
idto do its work once. GET /webhooks/deliverieslists them, andPOST /webhooks/deliveries/{id}/replaysends one again.POST /webhooks/{id}/testsends apingnow.