Execute API
Webhooks
Get a signed POST every time an address you created changes.
Setup
Set your endpoint in the dashboard. It must be a public https URL. Every address your account creates, with its API key or its client ID, reports there.
Events
| Type | Sent when |
|---|---|
address.detected | A payment to the address was seen onchain. It counts once confirmed. |
address.funded | The address holds the amount, and Execute is executing it. |
address.failing | A call reverts. Execute retries until expiry; status.message says why. |
address.settled | Every call ran. |
address.refunding | Past expiry with a balance, which is on its way to recovery. |
address.refunded | The balance went to recovery. |
address.expired | Expired with nothing to return. |
Payload
{
"id": "01925f3c-7d2e-7b41-9a8c-2f5e8d1b4c60",
"type": "address.settled",
"createdAt": "2026-09-29T07:12:31Z",
"sequence": 7,
"address": "0x3a9012C8bbDA720CE9C94458f8753965dFB6455a",
"chainId": 8453,
"status": {
"address": "0x3a9012C8bbDA720CE9C94458f8753965dFB6455a",
"state": "settled",
"transactionHash": "0x5e0c…",
"blockNumber": 36120455,
"updatedAt": "2026-09-29T07:12:31Z"
},
"data": {}
}idstring · UUID- The event’s id. Deliveries can repeat: skip an id you’ve already handled.
typestring- One of the events above.
createdAtstring · RFC 3339- When the event happened.
sequenceinteger- Grows with every change to the address. Deliveries can arrive out of order: keep the one with the highest sequence.
addressaddress- The executable address.
chainIdinteger- Its chain.
statusobject- The address’s status as of the event, exactly as Get an address’s status returns it.
dataobject- Details of the event, for diagnostics. They vary by event: rely on status.
Headers
| Header | Value |
|---|---|
X-Execute-Signature | t=<unix seconds>,v1=<hex>: see below. |
X-Execute-Event-Id | The event’s id. |
X-Execute-Event-Type | The event’s type. |
X-Execute-Delivery-Attempt | 1 on the first delivery, then 2, 3… |
Verifying signatures
v1 is the HMAC-SHA256 of <t>.<raw body>, keyed with your signing secret from the dashboard, as hex. Check it against the body exactly as received, before parsing it, and reject timestamps more than five minutes old so a captured delivery can’t be replayed:
import { createHmac, timingSafeEqual } from "node:crypto";
/** Whether a webhook is from Execute: the signature covers "<t>.<body>", and t is recent. */
export function verifyWebhook(rawBody: string, signature: string, secret: string, toleranceSecs = 300): boolean {
const parts = new Map(signature.split(",").map((part) => part.trim().split("=", 2) as [string, string]));
const t = Number(parts.get("t"));
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.get("v1") ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}Delivery and retries
Answer with any 2xx within 10 seconds. Anything else is retried with exponential backoff, from a second up to an hour between attempts, for 24 hours. Handle events idempotently: the same id can arrive more than once.