Skip to main content
agentaos.webhooks makes no network call, verify() runs entirely in your process. It checks a payload’s HMAC-SHA256 signature against your webhook secret, rejects stale or malformed signatures, and returns the parsed, camelCased event. Configure the destination URL and copy the signing secret from Settings → Developers → Webhooks in the dashboard (or set webhookUrl per checkout/link, see Checkouts).

webhooks.verify(payload, signature, secret, toleranceSec?)

string | Buffer
required
The raw, unparsed request body. If it’s a Buffer, it’s decoded as UTF-8 before verifying.
string
required
The x-agentaos-signature header value.
string
required
Your webhook signing secret (whsec_...).
number
default:"300"
Max age of the signature’s timestamp, in seconds. A signature older than this (or with a timestamp in the future) throws WebhookVerificationError.
Returns a typed WebhookEvent, throws WebhookVerificationError on any failure.

Signature format

The header is t=<unix_timestamp>,v1=<hmac_hex>. verify():
  1. Parses t and v1 out of the header, throws if either is missing.
  2. Checks now - t is between 0 and toleranceSec seconds, throws Webhook signature expired if not (this rejects both stale replays and clock-skewed future timestamps).
  3. Recomputes HMAC-SHA256(secret, "${t}.${payload}") and compares it to v1 with crypto.timingSafeEqual (constant-time, no early-exit on mismatch).
  4. JSON.parses the payload and deep-transforms it from snake_case to camelCase before returning it as a WebhookEvent.
Every failure at any step throws WebhookVerificationError, there’s no partial or “unverified” event returned.

WebhookEvent

A discriminated union on type. switch/narrow on it to get typed data.
string
Unique event ID, evt_<uuid>. Present on every event, both on the wire and on the object verify() returns.
'checkout.session.completed' | 'send.completed' | 'send.failed'
Inside every event’s data, amount is a string, e.g. "49.99", not the number you get back from checkouts.create() or paymentLinks.create(). Parse it before doing math. See the money model.

checkout.session.completed

Fires when a checkout session’s payment confirms.
The payment link’s secure ID.
string
string
e.g. "49.99".
string
string
On-chain settlement hash.
string
Payer wallet address.
'human' | 'agent'
string
CAIP-2 network ID.
object
Whatever metadata you passed at checkouts.create().

send.completed

Fires when an outbound send (a payout or transfer you initiated) is broadcast successfully.
string
string
string
string
string
string
number
string
string | null

send.failed

Same shape as send.completed, but txHash is always null and no broadcast succeeded.
string
null
string
string
string
string
number
string
string | null
successUrl is best-effort, the customer might close their browser before the redirect lands. Fulfill orders from the webhook (server-to-server, reliable), not the redirect.
Treat delivery as at-least-once. Use event.data.sessionId (or transactionId) to check whether you’ve already processed it before fulfilling again.
If your endpoint is ever down, poll checkouts.retrieve() for status === 'completed' as a backstop. Don’t build your primary flow on polling, it’s slower and burns rate limit budget.

Next steps

Checkouts

Set webhookUrl to receive checkout.session.completed.

Errors

WebhookVerificationError and the rest of the error hierarchy.