# Snipcart Webhooks

## When to Use This Skill

* How do I receive Snipcart webhooks?
* How do I verify a Snipcart webhook is authentic? (there is no signature — read below)
* What is the `X-Snipcart-RequestToken` header and how do I validate it?
* Why does `https://app.snipcart.com/api/requestvalidation/{token}` return 404 or 401?
* How do I handle `order.completed` or `v3/subscription.invoice.payment.succeeded`?
* How do I implement the `shippingrates.fetch` / `taxes.calculate` webhooks?

## Verification (core) — callback validation, NOT HMAC

Snipcart does not sign webhook payloads. There is no signature header, no HMAC,
no shared webhook secret and no timestamp header. Every outbound request instead
carries a random token in `X-Snipcart-RequestToken`, valid for one hour,
which you prove genuine by calling Snipcart's API with your secret API key
(HTTP Basic, key as the username, trailing colon, no password). 200 = genuine.

```javascript
const PATTERN = /^[A-Za-z0-9_-]{1,128}$/;  // token is attacker-controlled: format-check
                                           // BEFORE it reaches the URL ('..' would
                                           // resolve to a different API endpoint)
async function validateRequestToken(token, secretKey) {
  if (!secretKey) throw new Error('SNIPCART_SECRET_API_KEY is not set'); // never fail open
  const t = (token || '').trim();
  if (!PATTERN.test(t)) return false;      // missing/malformed: reject, no API call
  const res = await fetch(`https://app.snipcart.com/api/requestvalidation/${t}`, {
    headers: {
      Authorization: `Basic ${Buffer.from(`${secretKey}:`).toString('base64')}`,
      Accept: 'application/json',
    },
    redirect: 'manual',                    // never forward the secret key to a redirect
    signal: AbortSignal.timeout(5000),
  }).catch(() => null);                    // network error/timeout -> fail closed
  return res?.status === 200;              // 404 = unknown/expired/already validated
}                                          // 401 = secret key missing/wrong (per Snipcart support)

```

Only after a 200 should you `JSON.parse` the raw body and dispatch. Respond
200 with a JSON body — Snipcart requires `Content-Type: application/json`
and status 200.

> For complete handlers with route wiring, event dispatch, the synchronous
> shipping/taxes webhooks, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/snipcart-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/snipcart-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/snipcart-webhooks/examples/fastapi/)

## Event Envelope

Every webhook shares the same envelope. There is no event id and no delivery id —
for idempotency, key on the order token + `eventName` (+ `createdOn`). The order
token is `content.token` on Order-content events, `content.orderToken` on refund /
notification / withdrawal events, and `content.order.token` / `content.subscription.id`
on subscription events.

```json
{
  "eventName": "order.completed",
  "mode": "Live",
  "createdOn": "2026-09-28T14:03:11.000Z",
  "content": { }
}

```

Snipcart may add new fields to payloads at any time without notice or a new
version; existing fields are not renamed or removed. Ignore unknown fields —
do not apply strict schema validation.

## Common Event Types

| Event | Fires when | `content` |
| --- | --- | --- |
| `order.completed` | A new order is completed | Order |
| `order.status.changed` | Order status changes (adds top-level `from` / `to`) | Order |
| `order.paymentStatus.changed` | Payment status changes (adds top-level `from` / `to`) | Order |
| `order.trackingNumber.changed` | Tracking set (adds top-level `trackingNumber` / `trackingUrl`) | Order |
| `order.refund.created` | Order refunded | Refund |
| `order.notification.created` | Notification added to an order | Notification |
| `order.withdrawal.created` | EU withdrawal request submitted | Withdrawal |
| `v3/subscription.invoice.payment.succeeded` | Recurring subscription payment succeeds | `{ order, subscription }` |
| `v3/subscription.invoice.payment.failed` | Recurring subscription payment fails | `{ order, subscription }` |
| `v3/subscription.state.cancellationRequested` | A merchant or customer cancels (stays in this state until the end of the billing cycle) | `{ subscription }` |
| `v3/subscription.state.cancelled` | Subscription is cancelled | `{ subscription }` |

> The `v3/` prefix is part of the event name string — do not strip it.
> Subscription payment events do not fire for the first payment, only recurring ones.

Synchronous webhooks (configured separately, response body consumed at checkout):
`shippingrates.fetch` → respond `{"rates":[…]}`; `taxes.calculate` → respond `{"taxes":[…]}`.
See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/snipcart-webhooks/references/overview.md).

## Environment Variables

```bash
SNIPCART_SECRET_API_KEY=your_secret_api_key   # a SECRET key created in your merchant dashboard (never the public key)

```

Keys are per-mode: a Test-mode key cannot read Live data and vice versa. Configure
the key matching the mode of the webhooks you receive (the payload's `mode` is
`"Test"` or `"Live"`).

## Local Development

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

```

Hookdeck's `SNIPCART` source type takes the Snipcart secret API key and performs
this same token validation at ingestion. If Hookdeck verifies the token for you,
your destination should not re-validate the same token (it may already be
consumed, and will be expired on a retry more than an hour later) — verify the
`x-hookdeck-signature` header at the destination instead.

`shippingrates.fetch` and `taxes.calculate` must point directly at your app:
Hookdeck cannot synchronously return a destination's response to the client.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/snipcart-webhooks/references/overview.md) - Snipcart webhook concepts, full event list, payloads
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/snipcart-webhooks/references/setup.md) - Dashboard configuration and API key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/snipcart-webhooks/references/verification.md) - Request-token validation details and gotchas