Overview
- Webhooks are set per agent card. A card with no endpoint sends nothing.
- Each delivery is a signed JSON
POSTto your HTTPS endpoint. - You verify the
X-AgentPay-Signatureheader with the card’s signing secret. - Failed deliveries are retried up to five times in total. Queuing an event is best effort, so treat webhooks as alerts, not a complete ledger.
Webhooks are available on the Plus and Enterprise plans. On Free, an endpoint
cannot be saved, and an endpoint saved before a downgrade receives nothing until
you upgrade.
Set up a webhook
1
Add your endpoint
Open the agent card in the dashboard. Under Notification webhook, enter your
URL in Endpoint URL and click Save.The URL must use
https:// and point at a public host. Local, private and
internal addresses are refused.2
Copy the signing secret
Saving the first URL creates the card’s Signing secret, which starts with
whsec_. It appears under the endpoint. Store it with your server’s other
secrets.3
Verify every delivery
Check the signature before you act on a delivery. See
Verify a delivery. An endpoint that skips this accepts
writes from anyone who finds its URL.
4
Send a test
Click Send test. Agent Vault delivers a
card_fill event with
data.test: true and data.host: "webhook.test". No card is used and no email
is sent. Recent deliveries shows the result.

The Notification webhook block on an agent card.
Events
Each delivery is about one agent card.
Other account events, such as security code prompts, challenge handoffs, API key
activity and login changes, are not sent to agent card webhooks. Agent Vault
records them in the dashboard’s activity feed on a best effort basis: an event
that fails to save is dropped, not retried. It also emails them, but only when
email notifications are on and that event’s switch is on in
notification settings.
Email notification preferences do not affect webhooks. Every event above is
queued for delivery while the endpoint is set. Queuing is best effort: if
recording or signing an event fails, it is dropped before a delivery exists, and
nothing retries it. The retries under Delivery and retries
apply only to deliveries that were queued.
Payload
string
The event id. Stable across retries. Use it to deduplicate.
string
One of the event types above.
number
When the event happened, in milliseconds since the Unix epoch.
string | null
The agent card this delivery is for. When several agent cards govern the same
funding card, each one with an endpoint receives its own delivery.
object
Event details. The fields depend on the event type, as in the table below. New
fields are added without a version change, so ignore any you do not
recognise, and do not assume a field is present because an earlier delivery
had it.
field says which card value the event is about: "card" for the card number,
"cvv" for the security code. When both are filled for one checkout, you
normally get one card_fill, not two. cardId is an opaque id for the funding
card, never its number.
When the account holds more agent cards than its plan allows, a refused fill
reaches each agent card’s webhook with
data.reason set to
account_plan_quota. The account’s plan caused the refusal, not necessarily
the card the delivery is addressed to.What a payload contains
Payloads carry purchase and correlation context. Treat your endpoint and its logs as holding spending data:- Who and where: the agent card’s name, the merchant host, and on
approval_requiredanyrelatedHosts. - Money: on
approval_required, the checkoutamountand the card’s approvalthresholdwhen known, plusamountWithheld(the amount was not shared because the checkout was not in US dollars) andsplitTender(the amount is the order total and the card is charged less) when they apply.declaredAmountanddeclaredCurrencyare the total the agent said it expects, andpageAmountandpageCurrencythe total read on the page, each in its own currency. Areasonofamount_mismatchmeans the page’s total is over a spending limit and the agent’s, under half of it, fits.limit_thresholdnames the limitwindowthat was hit. - Correlation ids: on
card_fillanddenied_fill,cardId(an opaque id for the funding card, never its number) andsessionId(the proxy session the fill ran in). Withhostandfield, these let you match the two halves of one checkout and join deliveries to your own records. - Why: the refusal or approval
reason.
What a payload never contains
No card number, security code, password, 2FA secret or identity value, and no approval link.Verify a delivery
Every delivery carries these headers:timestamp is Unix seconds. signature is the lowercase hex HMAC-SHA256 of the
string <timestamp>.<raw body>, keyed with the card’s signing secret. Each retry
is signed again with a fresh timestamp.
1
Read the raw body
Use the exact bytes you received, before any JSON parsing. A re-serialised body
produces a different signature.
2
Split the header
Split
X-AgentPay-Signature on commas, then each part on the first =. Read t
and v1. Reject the request if either is missing.3
Check the timestamp
Reject the request if
t is more than five minutes from your current time. This
limits replay.4
Compute the expected signature
HMAC-SHA256 the string
t + . + raw body with your signing secret, and hex
encode the result.5
Compare in constant time
Compare your result with
v1 using a constant-time comparison, never ===.
Accept the delivery only if they match.Delivery and retries
A pending delivery is dropped if the card’s endpoint is changed or removed, the
card is deleted, or the account moves to a plan without webhooks.
Build your endpoint to:
- Return a
2xxquickly and do the work afterwards. - Deduplicate on
id. A retry repeats the same event. - Accept events out of order.
- Ignore unknown
typevalues and unknowndatafields.