# Etsy Webhooks

## When to Use This Skill

* How do I receive Etsy webhooks?
* How do I verify Etsy webhook signatures?
* How do I handle `order.paid` or `order.shipped` events from Etsy?
* How do I react to `listing.updated` or `inventory.out_of_stock` for my Etsy shop?
* Why is my Etsy webhook signature verification failing?

## How Etsy Webhooks Work

Etsy webhooks follow the Standard Webhooks spec. Every delivery is an HTTP POST with a JSON body and three headers:

| Header | Contents |
| --- | --- |
| `webhook-id` | Unique message ID (stays the same across retries) |
| `webhook-timestamp` | Unix timestamp (seconds) when the event was emitted |
| `webhook-signature` | Space-separated list of `v1,<base64 signature>` entries |

The signature is HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body}`, keyed with the signing secret after you strip the `whsec_` prefix and base64-decode the rest. The result is base64-encoded. Etsy recommends rejecting timestamps more than 300 seconds from your server time.

Payloads are thin: they carry `event_type`, `shop_id`, and a `resource_url` you call (with your OAuth token) to fetch the full receipt, listing, or product.

## Verification (core)

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

function verifyEtsyWebhook(rawBody, headers, secret) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signatureHeader = headers['webhook-signature'];
  if (!id || !timestamp || !signatureHeader) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // replay protection

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = crypto.createHmac('sha256', key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest('base64');

  // Header may hold several "v1,<sig>" entries (e.g. during secret rotation)
  return signatureHeader.split(' ').some((entry) => {
    const [version, sig] = entry.split(',');
    if (version !== 'v1' || !sig) return false;
    const a = Buffer.from(sig);
    const b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });
}

```

Etsy has no webhook SDK. The Node examples use the `standardwebhooks` package (`new Webhook(secret).verify(rawBody, headers)`), which implements exactly this algorithm with a 5-minute tolerance. The FastAPI example verifies manually.

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

## Common Event Types

| Event | Triggered When |
| --- | --- |
| `order.paid` | An order receives payment |
| `order.canceled` | A seller cancels an order |
| `order.shipped` | Shipping information is created for a receipt |
| `order.delivered` | An order is marked as delivered |
| `listing.created` | A listing is created |
| `listing.updated` | Title, description, personalization, taxonomy, images, attributes, or state change |
| `listing.deleted` | A listing is deleted |
| `inventory.created` | A product (listing variant) is created |
| `inventory.updated` | Price, readiness state, SKU, or state of a product changes |
| `inventory.deleted` | A product is deleted |
| `inventory.out_of_stock` | A product's quantity reaches 0 |

Note the spelling: `order.canceled` (one "l"). See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/etsy-webhooks/references/overview.md) for payload shapes.

## Environment Variables

```bash
ETSY_WEBHOOK_SECRET=whsec_xxxxx   # From Developer Portal → Manage your apps → Webhook portal → endpoint

```

## Local Development

```bash
npx hookdeck-cli listen 3000 etsy --path /webhooks/etsy

```

No account required. Use the tunnel URL as the endpoint URL in the Etsy Webhook portal, then send a test event from the endpoint's **Testing** tab.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/etsy-webhooks/references/overview.md) - Etsy webhook events and payload structure
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/etsy-webhooks/references/setup.md) - Configure endpoints in the Etsy Webhook portal, get the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/etsy-webhooks/references/verification.md) - Signature verification details and gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/etsy-webhooks/examples/) - Runnable Express, Next.js, and FastAPI examples with tests