# Agentcard Webhooks

## When to Use This Skill

* How do I receive Agentcard webhooks?
* How do I verify the `AgentCard-Signature` header?
* How do I handle `card.created`, `transaction.authorized`, `approval.requested`, `checkout_authorization.approved`, or `balance.low` events?
* Why does Standard Webhooks / svix verification fail on my Agentcard `whsec_` secret?
* Why is my Agentcard webhook signature verification failing?

## Essential Code (USE THIS)

Every Agentcard delivery carries `AgentCard-Signature: t=<unix seconds>,v1=<hex>`. `v1` is the HMAC-SHA256 (hex) of `"<t>.<raw body>"`. The key is the endpoint's **whole** secret string, `whsec_` prefix included, as UTF-8. **This is not Standard Webhooks**: don't use the `standardwebhooks` or `svix` libraries, and don't strip or base64-decode the prefix. `roll_secret` signs deliveries with the new secret from the next event, and Agentcard documents no overlap period, so update your secret promptly; the examples accept the header if any `v1` matches, which is harmless. The legacy `X-AgentCard-Signature: sha256=…` header signs the body alone and is not used by the examples.

### Verification (Node.js)

```javascript
const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyAgentcard(rawBody, header, secret) {
  if (!secret || !header) return false;
  let t; const v1 = [];
  for (const part of header.split(',')) {            // trim: a space after the comma is allowed
    const p = part.trim(), i = p.indexOf('=');
    if (i > 0) { const k = p.slice(0, i).trim(), v = p.slice(i + 1).trim();
      if (k === 't') t = v; else if (k === 'v1') v1.push(v); }
  }
  if (!t || !/^\d+$/.test(t) || v1.length === 0) return false;
  // Optional replay window, off by default: retries and replays keep the original timestamp
  const expected = crypto.createHmac('sha256', secret)   // whole whsec_... string
    .update(`${t}.`).update(rawBody).digest();
  // 64 hex chars first: Buffer.from(x, 'hex') silently truncates invalid hex
  return v1.some((s) => /^[0-9a-f]{64}$/i.test(s) &&
    crypto.timingSafeEqual(Buffer.from(s, 'hex'), expected));
}

```

### Verification (Python)

```python
import hashlib, hmac

def verify_agentcard(raw_body: bytes, header: str, secret: str) -> bool:
    if not secret or not header:
        return False
    t, v1 = None, []
    for part in header.split(","):
        k, sep, v = part.strip().partition("=")
        if sep and k.strip() == "t": t = v.strip()
        elif sep and k.strip() == "v1": v1.append(v.strip())
    if not t or not (t.isascii() and t.isdigit()) or not v1:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest().encode()
    # bytes on both sides: a non-ASCII header gives a rejection, not a 500
    return any(hmac.compare_digest(s.lower().encode(), expected) for s in v1)

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentcard-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentcard-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentcard-webhooks/examples/fastapi/).

### Timestamp window: off by default

Agentcard suggests rejecting timestamps older than a few minutes. **Hookdeck retries and replays resend the original timestamp, so a 5-minute window rejects late retries.** The examples therefore skip the freshness check unless you set `AGENTCARD_TIMESTAMP_TOLERANCE_SECONDS` to a positive number of seconds (default `0` = off). Use the event `id` for idempotency instead. Agentcard's own `verifyAgentcardWebhook` in `@agent-cards/commerce-sdk` defaults to a 300-second tolerance, so pass a larger `toleranceSeconds` if you use it behind Hookdeck. [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/verification.md) shows exactly where the window can be dropped or widened.

## Setup with Hookdeck

Paste the endpoint's whole secret, including the `whsec_` prefix, into the Hookdeck source. Agentcard uses it as a plain HMAC key. The secret is returned once by `POST /api/v2/webhook_endpoints` (and again by `GET /api/v2/webhook_endpoints/{id}/secret`). Use the Hookdeck source URL as the endpoint `url`. See [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/setup.md).

## Event Envelope and Types

Every event is `{ id, type, created, livemode, data }`. Branch on `type`; delivery is at least once, so dedupe on `id`. Agentcard waits 10 seconds for your answer, and redelivers after anything other than a `2xx`.

| Event | Triggered When |
| --- | --- |
| `card.created` | A one-time virtual card was created |
| `card.updated` | The card's balance or status changed |
| `card.closed` | The card closed |
| `transaction.authorized` | A merchant authorized a charge on the card |
| `approval.requested` | An action on a user's card needs their approval |
| `checkout_authorization.approved` | The user approved a checkout and the processor answered |
| `balance.low` | A card's balance dropped below its threshold |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/overview.md) for payloads and the other event families.

## Important Headers

| Header | Description |
| --- | --- |
| `AgentCard-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` |
| `X-AgentCard-Signature` | Legacy `sha256=…` over the body alone; not used here |

## Environment Variables

```bash
AGENTCARD_WEBHOOK_SECRET=whsec_your_endpoint_secret   # whole secret incl. whsec_, used as a plain key
AGENTCARD_TIMESTAMP_TOLERANCE_SECONDS=0               # optional; a positive value opts in to the replay window

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 agentcard --path /webhooks/agentcard

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/overview.md) - Agentcard webhook concepts, events, payloads, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/setup.md) - Creating the endpoint through the API, the secret, Hookdeck setup
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentcard-webhooks/references/verification.md) - Signature details, the timestamp window, and debugging