livemode: false; live activity carries livemode: true. A destination belongs to one mode: it’s created in the mode you’re viewing and receives only that mode’s events — register one in each mode to run both.
Register an endpoint
Webhook destinations live in the dashboard: open your organization → Webhooks → Add destination, pick the events, and paste your endpoint URL. Subscribe to All events, a whole family liketransaction.*, or individual events — and change the selection any time.
Creating a destination reveals its signing secret (prefixed whsec_) — store it, you use it to verify every delivery’s signature. You can reveal it again from the destination’s page, and rotate it there too (for example after a suspected leak). The same page sends test events so you can prove your handler before any real traffic, pauses or deletes the destination, and shows a live view of recent deliveries with their response codes.
The event payload
Every delivery is a POST with this envelope. Dedupe onid — the same event can be delivered more than once.
Verify the signature
Every delivery carries anAgentCard-Signature header so you can confirm it came from us and wasn’t replayed. The header looks like:
v1 is an HMAC-SHA256 of "{t}.{raw_request_body}" keyed with your endpoint secret. Verify it against the raw body (before any JSON parsing), and reject timestamps that are too old to blunt replays.
A legacy
X-AgentCard-Signature: sha256=<hex-hmac> header (an HMAC of the body alone, no timestamp) is also sent for backward compatibility. New integrations should verify AgentCard-Signature — the timestamp gives you replay protection.2xx to acknowledge. Any other status (or a timeout) is treated as a failure and retried.
Delivery and retries
We attempt each delivery up to 5 times with backoff on any non-2xx response, timeout, or network error. Requests time out after 10 seconds. The destination’s page in the dashboard shows every recent delivery — status, attempt count, the response code we got, and when the next retry is scheduled.
An endpoint that keeps failing is eventually disabled; fix your handler, then re-enable the destination from its page.
Event catalog
Subscribe to any of these by name (or"*" / a "family.*" prefix). Events are grouped below by the flow they belong to — each one shows a full example delivery. The envelope is always the same; only data changes shape.
Card events
The lifecycle of a card: issued, balance or status changes, closed.card.closed
A card was closed.reason tells you why: manual (closed via the API), single_use_spent (a single-use card made its purchase), balance_exhausted (a multi-use card spent its full limit), or provider_closed (closed upstream by the card provider).
card.created
A card was issued — via the API, or minted at the end of a company-funded flow. When the card was company-funded,transfer_id carries the transfer it was minted for so you can correlate it to your collection; it’s null for direct issuance.
card.updated
A card’s balance or status changed: the balance moving after a transaction, or a multi-use card being paused, resumed, or given a new spend limit (status is PAUSED while the card is paused). Not fired when the change is the card closing; that transition is covered by card.closed.
Cardholder and onboarding events
The people you issue cards to — created directly via the API or through a hosted onboarding session.cardholder.created
A cardholder was created — viaPOST /api/v1/cardholders, or by a hosted onboarding session that created a brand-new cardholder (claims of an existing one don’t re-fire this).
cardholder.updated
A cardholder’s details changed.updated_fields lists exactly which fields the update touched.
cardholder_onboarding_session.completed
A hosted onboarding session finished.cardholder_id is the resulting cardholder, external_user_id is your id for the user, and state echoes the opaque value you passed when creating the session. claimed_existing_cardholder is true when the session attached to a cardholder already in your org’s books instead of creating a new one.
platform_connect_session.completed
A company finished your hosted platform connect flow. The payload never carries credentials — treat it as a nudge, verifystate, and exchange the code from the redirect server-side (or poll the session by id).
Transaction events
The life of a charge: authorized (funds held) → cleared (settled), or declined / voided.balance.low rides along when a charge leaves the card nearly empty.
balance.low
A transaction left the card’s remaining balance at $10 or less (but above zero). Fires alongside the transaction event, so you can top up or warn the user before the next charge declines.transaction.authorized
A charge was authorized on a card — the funds are held.id is the provider’s transaction id (dedupe transactions on it; it stays stable through clearing), and balance_cents is the card’s remaining balance after the hold.
transaction.cleared
An authorized charge settled. Same shape astransaction.authorized, with status now final.
transaction.declined
A charge was declined — no funds moved.decline_reason says why (only present on declines).
transaction.voided
An authorization was reversed before settling — the held funds return to the card’s balance.Merchant connection events
Cardholders linking merchant accounts for agent-driven shopping.merchant.connected
A cardholder linked a merchant account (e.g. DoorDash) — useful to proactively tell the user their connection is live. Fires on live activity only.Reward events
Tokenback your organization routes to its users. Configure the routed share withrewardShareBps (0 to 10000 basis points of your revenue share) on PATCH /orgs/:orgId/revenue-config.
reward.earned
A settled charge granted tokens to a connected user out of your routed reward share.tokens counts only the tokens your share funded (1 token = 1 cent of credit value), user_id is the user who earned them, and card_id is the card whose settlement triggered the grant.
Company wallet events
The company wallet funding flow, in firing order:card_flow.started (hold your customer) → transfer.approved → transfer.initiated → transfer.completed (capture your customer) → card.created. The failure and release paths (transfer.failed, card_flow.failed, transfer.released) and the pool treasury events (wallet.funded, wallet.balance.low) round out the family.
card_flow.failed
The card mint failed after funds had already moved. You keep your capture:funds_status: released_to_headroom means the money is real spending power for that cardholder and automatically nets against their next company-funded card.
card_flow.started
A user’s company-funded card creation reserved funds in your pool — your moment to collect from your customer (company wallet). Whenack_required is true, confirm collection via POST /api/v1/wallet/transfers/:id/collection_succeeded before ack_deadline; with auto-approve it’s informational.
transfer.approved
Collection was confirmed for a company-funded card — the pool transfer proceeds.approved_via says how it was confirmed.
transfer.completed
The funds became the user’s spending power — the funding is final from here. Capture your customer’s payment on this event.transfer.failed
Company funding aborted before the card was created — release your hold on the customer.reason is one of insufficient_org_funds, ack_timeout, ack_denied, or transfer_failed.
transfer.initiated
The pool-to-user transfer was submitted on-chain.tx_hash is the transaction hash when known — null in sandbox and for fully headroom-netted transfers where nothing moves on-chain.
transfer.released
A company-funded card closed with unused balance — you can release or refund your own hold forreleased_cents. The unused amount stays as the user’s spending power and offsets their next company-funded card.
wallet.balance.low
Your company wallet dropped under the low-balance threshold you configured. Sent at most once per alert window, not on every check — top up before card creations start failing.wallet.funded
A USDC deposit landed in your company wallet and was booked.amount_cents is the deposit, balance_cents the pool’s on-chain balance after it.
Identity verification events
The identity verification flow. One event follows a connected user’s verification from start to finish — drive your UI from it instead of pollingGET /api/v2/kyc.
identity.verification.updated
Fires whenever a connected user’s verification status changes.user_id is the connected user. status is one of awaiting_documents, needs_information, requires_verification, pending, approved, or rejected. When the status is needs_information, required_fields lists exactly what to collect next; when it’s requires_verification, iframe_url is the face-scan URL to show the user (short-lived — always render the freshest one, don’t store it). Non-terminal statuses (and rejected) may also carry reason — an end-user-safe sentence explaining what the review asked for, safe to show verbatim.
Statuses are not one-way: a review can send the user back — pending may return to needs_information (a detail didn’t match the document; re-collect and resubmit the listed fields) or to awaiting_documents (unusable images; upload both sides again). Drive your UI from the current status of every event — see when a review sends the user back.
A rejected event can additionally carry reason_code: "duplicate_identity": the person is already verified on another Agentcard account. That rejection is not fixed by new documents; the user resolves it by linking the other account from the verification page, and the same verification then reads approved on the next poll. See duplicate documents.
To exercise this event end to end with a test-mode credential, simulate an outcome — the simulated verdict fires the same event (with livemode: false) to your test-mode endpoints.
reason to show the user:
reason_code: