webhooks.verify() runs in your process and makes no network call. It checks the payload’s HMAC-SHA256 signature against your webhook secret, rejects a stale or malformed signature, and returns the parsed event with camelCase keys. Set the destination URL and copy the signing secret in Settings → Developers → Webhooks, or set webhookUrl on a checkout or link.
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 throws
WebhookVerificationError. A timestamp in the future throws too, by any amount: if your server’s clock runs behind AgentaOS’s, every event fails, so keep the clock synced (NTP).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. verify() never returns a partial or unverified event.
WebhookEvent
A discriminated union on type. switch/narrow on it to get typed data.
'checkout.session.completed' | 'send.completed' | 'send.failed' | 'subscription.created' | 'subscription.renewed' | 'subscription.payment_failed' | 'subscription.updated' | 'subscription.canceled' | 'dispute.created' | 'dispute.closed' | 'account.updated' | 'webhook.test'
string
The id of the business the event happened in: your own, or one of the businesses you manage. Present on every event.
id for each event, but the WebhookEvent type does not declare it yet. Deduplicate on the keys in Deduplication.
Amounts follow the money model: amount in data is a string, and amountMinor is an integer in minor units.
checkout.session.completed
Fires when a checkout is paid or a free trial starts. The full field list, with the wire names, is in Events.
string
The payment link’s public token from its
checkoutUrl, not its id. null for a checkout created without a link, although the type says string.string
string
e.g.
"49.99".string
The currency the buyer was charged in: the fiat currency (
EUR, USD) for a card or bank checkout, the token (USDC) for a wallet checkout.string
On-chain settlement hash.
null for a card or bank sale, although the type says string.string
Payer wallet address.
null for a card or bank sale, although the type says string.'human' | 'agent'
string
card, bank_transfer, or a CAIP-2 network ID for a stablecoin payment.boolean
true in live mode, false in test mode.object
The
metadata you set on the checkout or payment link, with camelCase keys. metadata.customerId is your own id if you set one. On a renewal, metadata is AgentaOS’s own: see Event order for subscriptions.Fields the type does not declare yet
verify() also returns rail, vendorReference, customerId, buyerEmail, buyerName and overpaidByCents on every checkout event, and trial and trialEnd when a free trial starts. customerId is AgentaOS’s id for the buyer, not your user id. CheckoutCompletedData does not declare these fields yet, so read them through a wider type:
metadata.trial is also true on a trial start, and needs no cast.
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 account’s webhook URL. For when each one fires, and in what order, see Event order for subscriptions.
string
The subscription’s id, a UUID: the
id that subscriptions.list() returns.string
The status after the change, one of the subscription statuses. A subscription that never activated sends no event, so
incomplete and incomplete_expired do not appear.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, a UUID. Not the token in the checkout URL that checkout.session.completed sends as linkId.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.dispute.created / dispute.closed
A buyer’s bank opened a chargeback on a sale, or closed it in your favor. A lost dispute sends no event. Both carry the same DisputeData.
string
string
The dispute’s status as the card processor reports it, e.g.
needs_response, or won on dispute.closed.string
The sale the dispute is about.
number
Integer minor units.
1240 means €12.40.string
boolean
true in live mode, false in test mode.account.updated
A business’s verification changed: its identity check landed, or we approved, held, resumed or rejected it, or asked it for changes. For a platform, business names the client it happened to. See Platform webhooks.
'unverified' | 'in_review' | 'verified' | 'on_hold' | 'rejected'
Where the business’s review stands, in the words the dashboard uses.
boolean
We have asked the business something and are waiting on it.
boolean
The identity check is done: its own, or its company’s for another app of the same company.
boolean
Always
true: verification is about live money.webhook.test
The sample event the dashboard’s Send test event button fires at your endpoint.
string
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
The customer can close the browser before the
successUrl redirect lands. Fulfill orders from the webhook, which your server receives directly, not from the redirect.Webhooks can be delivered more than once
Webhooks can be delivered more than once
Record a key per event, such as
event.data.sessionId or event.data.transactionId, and skip an event you have already handled. The key for each event type is in Deduplication.Poll as a fallback, not a replacement
Poll as a fallback, not a replacement
If your endpoint is down, poll
checkouts.retrieve() for status === 'completed' as a backstop. Do not build your main flow on polling: it is slower, and every call counts against your rate limit.Next steps
Checkouts
Set
webhookUrl to receive checkout.session.completed.Errors
WebhookVerificationError and the rest of the error hierarchy.