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.WebhookEvent, throws WebhookVerificationError on any failure.
Signature format
The header ist=<unix_timestamp>,v1=<hmac_hex>. verify():
- Parses
tandv1out of the header, throws if either is missing. - Checks
now - tis between0andtoleranceSecseconds, throwsWebhook signature expiredif not (this rejects both stale replays and clock-skewed future timestamps). - Recomputes
HMAC-SHA256(secret, "${t}.${payload}")and compares it tov1withcrypto.timingSafeEqual(constant-time, no early-exit on mismatch). JSON.parses the payload and deep-transforms it from snake_case to camelCase before returning it as aWebhookEvent.
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' | 'subscription.created' | 'subscription.renewed' | 'subscription.payment_failed' | 'subscription.updated' | 'subscription.canceled'
checkout.session.completed
Fires when a checkout session’s payment confirms.
string
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.
boolean
true in live mode, false in test mode.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
boolean
true in live mode, false in test mode.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
boolean
true in live mode, false in test mode.string | null
subscription.*
All five subscription.* events carry the same SubscriptionData, delivered to your organization webhook endpoint.
string
The subscription ID.
string
Stripe status, verbatim:
incomplete, active, trialing, past_due, unpaid, or canceled.string | null
The plan name, from the backing payment link.
string
number
Per-cycle price in integer minor units.
1000 means €10.00.string | null
ISO 8601 timestamp, or
null before the first cycle is set.boolean
true once a cancellation is scheduled for period end.string
The current plan’s payment-link id.
object | null
Null unless a downgrade is scheduled. Then
{ linkId, planName, amountMinor, effectiveAt }: the plan the subscription switches to at effectiveAt. subscription.updated fires when it is scheduled, reverted, and when it takes over.string | null
string | null
boolean
true in live mode, false in test mode.Don't fulfill on the success page redirect
Don't fulfill on the success page redirect
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.Webhooks can be delivered more than once
Webhooks can be delivered more than once
Treat delivery as at-least-once. Use
event.data.sessionId (or transactionId) to check whether you’ve already processed it before fulfilling again.Poll as a fallback, not a replacement
Poll as a fallback, not a replacement
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.