Agent skill

Etsy Webhooks Skill

Receive and verify Etsy webhooks. Use when setting up Etsy webhook handlers, debugging Etsy signature verification (Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature), or handling Etsy shop events like order.paid, order.shipped, order.canceled, listing.updated, or inventory.updated.

Install this skill

npx skills add hookdeck/webhook-skills --skill 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:

HeaderContents
webhook-idUnique message ID (stays the same across retries)
webhook-timestampUnix timestamp (seconds) when the event was emitted
webhook-signatureSpace-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)

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/, examples/nextjs/, examples/fastapi/.

Common Event Types

EventTriggered When
order.paidAn order receives payment
order.canceledA seller cancels an order
order.shippedShipping information is created for a receipt
order.deliveredAn order is marked as delivered
listing.createdA listing is created
listing.updatedTitle, description, personalization, taxonomy, images, attributes, or state change
listing.deletedA listing is deleted
inventory.createdA product (listing variant) is created
inventory.updatedPrice, readiness state, SKU, or state of a product changes
inventory.deletedA product is deleted
inventory.out_of_stockA product's quantity reaches 0

Note the spelling: order.canceled (one "l"). See references/overview.md for payload shapes.

Environment Variables

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

Local Development

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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →