Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions providers/agentcard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ notes: >
whsec_... but this is NOT Standard Webhooks: the HMAC key is the whole secret string including
whsec_, as UTF-8. Do NOT use the standardwebhooks or svix libraries and do not strip or
base64-decode the whsec_ prefix. t=<ts>,v1=<hex> header: split on ',' and trim each part (allow a
space after the comma), take t and every v1 (several are sent during secret rotation) and accept if
space after the comma), take t and every v1 and accept if
any v1 matches; reject a header with no t or no v1. Compare in constant time: in Node, check the
received value is exactly 64 hex chars (/^[0-9a-f]{64}$/i) before any Buffer.from(x, 'hex') (invalid
hex silently truncates), or compare the lowercase hex strings as equal-length UTF-8 Buffers with
Expand All @@ -21,7 +21,7 @@ notes: >
AGENTCARD_TIMESTAMP_TOLERANCE_SECONDS (default 0 = off), as the mercury-webhooks skill does.
SKILL.md must say that Hookdeck retries and replays resend the original timestamp, so a 5-minute
window rejects late retries, and point to references/verification.md, which shows where the window
can be dropped or widened. A legacy X-AgentCard-Signature: sha256=<hex> header signs the body alone;
can be dropped or widened. A legacy X-AgentCard-Signature: sha256=... header signs the body alone;
Hookdeck uses AgentCard-Signature, so the examples verify AgentCard-Signature only and say the
legacy header is not used. Events include card.created, card.updated, card.closed,
transaction.authorized, approval.requested, checkout_authorization.approved, balance.low. Hookdeck
Expand Down
10 changes: 6 additions & 4 deletions providers/appstle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ displayName: 'Appstle Subscriptions'
docs:
webhooks: https://developers.appstle.com/subscription/webhooks
notes: >
Appstle Subscriptions (Shopify subscriptions app). Delivered by Svix: svix-id, svix-timestamp,
svix-signature ('v1,<base64>', space-separated during rotation), HMAC-SHA256 over
Appstle Subscriptions (Shopify subscriptions app). Svix's scheme: svix-id, svix-timestamp,
svix-signature ('v1,<base64>', space-separated when there are several), HMAC-SHA256 over
'{svix-id}.{svix-timestamp}.{raw body}', key = base64-decode of the secret after whsec_. The secret
is in Settings > Webhooks in the Appstle dashboard. Use the svix library as Appstle's docs do. The
standardwebhooks / svix libraries always enforce a fixed 5-minute timestamp window. SKILL.md must
is in the webhook portal: More > Webhook Integration > Launch webhook portal > your endpoint. Appstle's
docs show a manual verifier that rejects timestamps over five minutes off, and each of Appstle's own
retries carries a new timestamp. The standardwebhooks / svix libraries implement the same scheme and
always enforce a fixed 5-minute timestamp window. SKILL.md must
say that Hookdeck retries and replays resend the original timestamp, so the library rejects late
retries after 5 minutes, and point to a manual verifier in references/verification.md (HMAC-SHA256
over '{id}.{timestamp}.{raw body}', key = base64-decode of the secret after whsec_, compare every
Expand Down
2 changes: 1 addition & 1 deletion providers/mercury.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ docs:
events: https://docs.mercury.com/reference/createwebhook
notes: >
Business banking. Header Mercury-Signature: t=<unix seconds>,v1=<hex>. v1 = HMAC-SHA256 hex over
"<t>.<raw body>" keyed with the endpoint's secretKey used as-is. Endpoints are created via the API
"<t>.<raw body>" keyed with the endpoint's secret used as-is. Endpoints are created via the API
(POST /webhooks); the secret is only returned on creation, so save it then. Hookdeck retries and
replays resend the original timestamp, so a 5-minute window rejects late retries: the timestamp
freshness check must be opt-in via MERCURY_TIMESTAMP_TOLERANCE_SECONDS (default 0 = off), as the
Expand Down
6 changes: 4 additions & 2 deletions providers/salesloft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ docs:
notes: >
Sales engagement platform. Header x-salesloft-signature: HMAC-SHA1 hex digest of the raw request
body, keyed with the subscription's callback_token (any string you choose when creating the
subscription through the Webhook Subscriptions API). The token is also echoed in the payload; the
header HMAC is the check to use. Subscriptions are API-only. Event types include person_created,
subscription through the Webhook Subscriptions API). The schema says the token is also in the
payload, but the one published body (link_swap) doesn't show it; the header HMAC is the check to
use. link_swap is synchronous: Salesloft waits for a JSON links map in the response. Subscriptions
are made through the API. Event types include person_created,
person_updated, call_created, email_updated, meeting_booked, cadence_membership_created and
others; check the docs. Express examples must read the raw body with express.raw({ type: '*/*' })
so any content type verifies.
Expand Down
10 changes: 5 additions & 5 deletions skills/agentcard-webhooks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ metadata:

## 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=<hex>` header signs the body alone and is not used by the examples.
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)

Expand Down Expand Up @@ -74,15 +74,15 @@ def verify_agentcard(raw_body: bytes, header: str, secret: str) -> bool:

### 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. [references/verification.md](references/verification.md) shows exactly where the window can be dropped or widened.
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](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](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`.
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 |
|-------|----------------|
Expand All @@ -100,8 +100,8 @@ See [references/overview.md](references/overview.md) for payloads and the other

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

## Environment Variables

Expand Down
2 changes: 1 addition & 1 deletion skills/agentcard-webhooks/examples/express/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const PORT = process.env.PORT || 3000;
// v1 is HMAC-SHA256 (hex) of "<t>.<raw body>". The key is the endpoint's WHOLE secret string,
// including the whsec_ prefix, as UTF-8. This is NOT Standard Webhooks: don't strip the prefix or
// base64-decode it, and don't use the standardwebhooks or svix libraries.
// The legacy X-AgentCard-Signature (sha256=<hex>, body only) header is not used here.
// The legacy X-AgentCard-Signature (sha256=..., body only) header is not used here.
// https://docs.agentcard.com/webhooks/overview

// Optional replay window in seconds. Off by default (0): retries and replays, including
Expand Down
2 changes: 1 addition & 1 deletion skills/agentcard-webhooks/examples/fastapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# v1 is HMAC-SHA256 (hex) of "<t>.<raw body>". The key is the endpoint's WHOLE secret string,
# including the whsec_ prefix, as UTF-8. This is NOT Standard Webhooks: don't strip the prefix or
# base64-decode it, and don't use the standardwebhooks or svix libraries.
# The legacy X-AgentCard-Signature (sha256=<hex>, body only) header is not used here.
# The legacy X-AgentCard-Signature (sha256=..., body only) header is not used here.
# https://docs.agentcard.com/webhooks/overview

# Optional replay window in seconds. Off by default (0): retries and replays, including
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import crypto from 'crypto';
// v1 is HMAC-SHA256 (hex) of "<t>.<raw body>". The key is the endpoint's WHOLE secret string,
// including the whsec_ prefix, as UTF-8. This is NOT Standard Webhooks: don't strip the prefix or
// base64-decode it, and don't use the standardwebhooks or svix libraries.
// The legacy X-AgentCard-Signature (sha256=<hex>, body only) header is not used here.
// The legacy X-AgentCard-Signature (sha256=..., body only) header is not used here.
// https://docs.agentcard.com/webhooks/overview

// Optional replay window in seconds. Off by default (0): retries and replays, including
Expand Down
6 changes: 3 additions & 3 deletions skills/agentcard-webhooks/references/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Agentcard issues virtual cards for AI agents. Everything that happens to your connected users reaches your server as a webhook: a card created, a charge authorized, a checkout approved. SDK callbacks are UI signals; webhooks are the record. Delivery is at least once, so deduplicate on the event `id`.

You register an endpoint with `POST https://api.agentcard.sh/api/v2/webhook_endpoints` (or in the dashboard under Developers → Webhooks) and list the events it should receive in `enabled_events`: exact names, prefix wildcards such as `card.*`, or `["*"]`. Endpoints are scoped to one mode: a sandbox token makes a sandbox endpoint (`livemode: false`), a production token a production one.
You register an endpoint with `POST https://api.agentcard.sh/api/v2/webhook_endpoints` (or in the dashboard under Developers → Webhooks) and list the events it should receive in `enabled_events`: exact names, prefix wildcards such as `card.*`, or `["*"]`. `["*"]` alone does not turn on `purchase.message`: list it by name or as `purchase.*`. Endpoints are scoped to one mode: a sandbox token makes a sandbox endpoint (`livemode: false`), a production token a production one.

## Common Event Types

Expand All @@ -18,7 +18,7 @@ You register an endpoint with `POST https://api.agentcard.sh/api/v2/webhook_endp
| `checkout_authorization.approved` | The user approved a paused checkout with their passkey and the processor answered | Continue the purchase, reconcile `charged_amount` |
| `balance.low` | A card's remaining balance dropped below its threshold | Top up or alert |

Other events exist and the examples log them as unhandled: `transaction.cleared`, `transaction.declined`, `transaction.voided`, `connection.created`, `vault.*`, `order.*`, `identity.verification.updated`, other `checkout_authorization.*` (submitted, declined, expired, amount_mismatch, settled, settlement_unconfirmed, refused, watched), `reward.*`, `merchant.connected`, and company-wallet events.
Other events exist and the examples log them as unhandled: `transaction.cleared`, `transaction.declined`, `transaction.voided`, `connection.created`, `vault.*`, `order.*`, `plan.updated`, `plan.item.updated`, `purchase.message`, `identity.verification.updated`, other `checkout_authorization.*` (submitted, declined, expired, amount_mismatch, settled, settlement_unconfirmed, refused, watched), `reward.*`, `merchant.connected`, and company-wallet events.

## Event Payload Structure

Expand Down Expand Up @@ -46,7 +46,7 @@ Every event has the same envelope; branch on `type` directly (it is part of the

## Delivery and Retries

Return a `2xx` quickly and do slow work afterwards. Failed deliveries retry up to five times: immediately, then after 1 minute, 5 minutes, 30 minutes and 1 hour. Each retry is a resend of the original request.
Agentcard waits 10 seconds for an answer, so return a `2xx` quickly and do slow work afterwards. Any other answer counts as a failure. Failed deliveries retry up to five times: immediately, then after 1 minute, 5 minutes, 30 minutes and 1 hour. The event `id` is the same on every redelivery.

## Full Event Reference

Expand Down
4 changes: 2 additions & 2 deletions skills/agentcard-webhooks/references/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ curl -X POST https://api.agentcard.sh/api/v2/webhook_endpoints \
```

- `url` (required): an HTTPS URL you control.
- `enabled_events` (required): exact names, prefix wildcards (`card.*`), or `["*"]`. An endpoint receives only what it lists; unknown types are rejected.
- `enabled_events` (required): exact names, prefix wildcards (`card.*`), or `["*"]`. An endpoint receives only what it lists; unknown types are rejected. `["*"]` alone does not turn on `purchase.message`: list it by name or as `purchase.*`.
- `description` (optional, up to 255 characters).

## Get Your Signing Secret
Expand All @@ -41,7 +41,7 @@ npx hookdeck-cli listen 3000 agentcard --path /webhooks/agentcard

## Testing

Sandbox events deliver exactly like production, with `livemode: false`. If a sandbox integration completes actions but nothing arrives, check that the endpoint was created with a sandbox token. The Agentcard CLI can also forward signed events to your machine (`agent-cards companies webhooks listen --forward-to localhost:4000/webhooks`) and send a test event (`agent-cards companies webhooks test`).
Sandbox events deliver exactly like production, with `livemode: false`. If a sandbox integration completes actions but nothing arrives, check that the endpoint was created with a sandbox token. To trigger events, `POST /api/v2/cards/{card_id}/test_charge` simulates a charge on a sandbox card and sends the same webhooks a real charge does.

Recent deliveries, with payloads and response codes, are listed by `GET /api/v2/webhook_endpoints/{id}/deliveries` and in the dashboard.

Expand Down
4 changes: 2 additions & 2 deletions skills/agentcard-webhooks/references/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ AgentCard-Signature: t=1757264400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0
- Key: the endpoint's **whole** secret string, including `whsec_`, as UTF-8
- Signed message: `"<t>.<raw request body>"`

A legacy `X-AgentCard-Signature: sha256=<hex>` header signs the body alone. Hookdeck uses `AgentCard-Signature`, and the examples verify only that header; the legacy header is not used.
A legacy `X-AgentCard-Signature: sha256=…` header signs the body alone. Hookdeck uses `AgentCard-Signature`, and the examples verify only that header; the legacy header is not used.

## This Is Not Standard Webhooks

Expand All @@ -40,7 +40,7 @@ ok = any(hmac.compare_digest(s.lower().encode(), expected) for s in v1_values)

## Where Does the Timestamp Window Go?

Agentcard's docs suggest rejecting timestamps older than a few minutes. The examples don't do this by default. **Hookdeck retries and replays resend the original request, including the original `t`**, so a 5-minute window rejects a legitimate late redelivery (for example after your receiver was down, or when you replay an event from the dashboard). Agentcard's own retries run for over an hour.
Agentcard's docs suggest rejecting timestamps older than a few minutes. The examples don't do this by default. **Hookdeck retries and replays resend the original request, including the original `t`**, so a 5-minute window rejects a legitimate late redelivery (for example after your receiver was down, or when you replay an event from the dashboard). Agentcard's own retries run for over an hour. Agentcard's `verifyAgentcardWebhook` (`@agent-cards/commerce-sdk`) rejects a timestamp more than 300 seconds from now unless you pass `toleranceSeconds`.

The check is opt-in: set `AGENTCARD_TIMESTAMP_TOLERANCE_SECONDS` to a positive whole number of seconds. In each example it lives in one place:

Expand Down
2 changes: 1 addition & 1 deletion skills/aimfox-webhooks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function verifyAimfoxRequest(headers, secret = process.env.AIMFOX_WEBHOOK_SECRET
the prefix.
- Look the header up case-insensitively. Return `401` when it is missing or differs.
- A static shared credential authenticates the sender, not the body. There is no signature and no
timestamp, so nothing stops a captured request being replayed. Use HTTPS and rotate the
signed timestamp, so nothing stops a captured request being replayed. Use HTTPS and rotate the
credential if it leaks.
- Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it. Return
`400` for a body that isn't a JSON object, `200` once handled.
Expand Down
11 changes: 8 additions & 3 deletions skills/aimfox-webhooks/references/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

## What Are Aimfox Webhooks?

Aimfox is a LinkedIn automation platform. Workspace webhooks (beta) push an HTTP `POST` with a JSON
body to your endpoint when events happen in the workspace, so you don't have to poll the API. You
can configure several webhooks and pick which events each one receives.
Aimfox is a LinkedIn automation platform. Workspace webhooks (beta) push a JSON body to your
endpoint when events happen in the workspace, so you don't have to poll the API. You can configure
several webhooks and pick which events each one receives. The docs don't name the HTTP method, so
the examples here assume `POST`.

Webhooks are not signed. The only authentication is the optional **Authentication Header** on the
webhook, sent with every request. See [verification.md](verification.md).
Expand Down Expand Up @@ -44,6 +45,10 @@ campaign message, and `new_reply` on any reply to any message on a workspace acc
- `workspace`: details of your workspace
- `event`: event-specific object

The docs list those four fields, and every example on the events endpoint also has a top-level ISO 8601
`timestamp`. `event.timestamp` is ISO 8601 in the examples too, except on `account_logged_out`, where it
is a number in milliseconds. Nothing authenticates either value.

## Retries

A failed delivery is retried up to 6 times: after 1 minute, then 5 minutes, 30 minutes, 1 hour,
Expand Down
Loading
Loading