> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentcard.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> How your server finds out what happened in the wallet: the envelope, the signature, and every event we send.

Everything that happens in the wallet reaches your server as a webhook. The SDK callbacks are UI signals; webhooks are the record. Create endpoints in the [dashboard](https://app.agentcard.sh) under **Webhooks**, or with `agent-cards companies webhooks create`.

## The envelope

Every event has the same shape:

```json theme={null}
{
  "id": "evt_...",
  "type": "connected_card.updated",
  "created": "2026-08-11T18:30:00.000Z",
  "livemode": false,
  "data": { ... }
}
```

Delivery is at least once, so the same event can arrive twice. Deduplicate on `id`.

## Verify the signature

Each delivery carries an `AgentCard-Signature` header:

```
AgentCard-Signature: t=1754938200,v1=5257a869e7...
```

To verify: take `t`, concatenate it with a dot and the raw request body, compute HMAC-SHA256 with your endpoint's signing secret, and compare it to `v1`. Reject anything older than a few minutes to block replays.

<CodeGroup>
  ```javascript Node theme={null}
  const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
  const expected = crypto
    .createHmac("sha256", process.env.AGENTCARD_WEBHOOK_SECRET)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  ```

  ```python Python theme={null}
  import hmac, hashlib, os

  t, v1 = (part.split("=", 1)[1] for part in header.split(","))
  expected = hmac.new(
      os.environ["AGENTCARD_WEBHOOK_SECRET"].encode(),
      f"{t}.{raw_body}".encode(),
      hashlib.sha256,
  ).hexdigest()
  valid = hmac.compare_digest(expected, v1)
  ```
</CodeGroup>

Read the signing secret in the dashboard, or with `companies webhooks reveal`. If it leaks, `companies webhooks roll-secret` rotates it.

There is also a legacy `X-AgentCard-Signature: sha256=...` header signing the body alone. New integrations should use `AgentCard-Signature`.

## Delivery and retries

Failed deliveries retry with backoff, and a sweeper picks up anything that got interrupted. Recent deliveries, with payloads and response codes, are visible in the dashboard and with `companies webhooks deliveries`.

Send yourself a test event any time:

```bash theme={null}
agent-cards companies webhooks test
```

## Every event we send

By default an endpoint receives everything. You can filter to specific types per endpoint.

| Object       | Events                                                                                                      |
| ------------ | ----------------------------------------------------------------------------------------------------------- |
| Connections  | `connection.created`                                                                                        |
| Added cards  | `connected_card.updated`                                                                                    |
| Identity     | `identity.verification.updated`                                                                             |
| Cards        | `card.created` · `card.updated` · `card.closed`                                                             |
| Cardholders  | `cardholder.created` · `cardholder.updated` · `cardholder_onboarding_session.completed`                     |
| Transactions | `transaction.authorized` · `transaction.declined` · `transaction.cleared` · `transaction.voided`            |
| Card flows   | `card_flow.started` · `card_flow.failed`                                                                    |
| Approvals    | `approval.requested`                                                                                        |
| Balance      | `wallet.funded` · `user_wallet.funded` · `wallet.balance.low` · `balance.low`                               |
| Transfers    | `transfer.initiated` · `transfer.approved` · `transfer.completed` · `transfer.failed` · `transfer.released` |
| Recoveries   | `recovery.requested` · `recovery.completed` · `recovery.rejected`                                           |
| Withdrawals  | `wallet.withdrawal.initiated` · `wallet.withdrawal.completed` · `wallet.withdrawal.failed`                  |
| Wallet links | `wallet_link.opened`                                                                                        |
| Rewards      | `reward.earned` · `reward.reversed`                                                                         |
| Merchants    | `merchant.connected`                                                                                        |
| Platform     | `platform_connect_session.completed`                                                                        |

## When it fails

If your endpoint is down, deliveries retry; nothing is lost, but act on the dashboard's failure indicators. If signatures stop verifying, check that you're reading the raw body (not re-serialized JSON) and the right endpoint's secret.

## Sandbox behavior

Sandbox events deliver exactly like production, with `livemode: false`. The quickstart's events are real deliveries you can inspect.

Next: [Purchase API](/purchase/purchase-api)
