Agent skill

Printify Webhooks Skill

Receive and verify Printify webhooks. Use when setting up Printify webhook handlers, debugging X-Pfy-Signature HMAC-SHA256 verification, creating webhook subscriptions through the Printify API, or handling print-on-demand events like order:created, order:shipment:created, order:shipment:delivered, product:publish:started, or shop:disconnected.

Install this skill

npx skills add hookdeck/webhook-skills --skill printify-webhooks


When to Use This Skill

  • How do I receive Printify webhooks (orders, shipments, product publishing)?
  • How do I verify the Printify X-Pfy-Signature header?
  • How do I create a Printify webhook and set a secret through the API?
  • How do I handle order:shipment:created or product:publish:started events?
  • Why is my Printify webhook signature verification failing?

How Printify Secures Webhooks

Printify webhooks are created through the API (POST /v1/shops/{shop_id}/webhooks.json), one per topic. The secret field is optional, but always set one: without it Printify sends no signature and anyone who finds your URL can post to it. Generate one with openssl rand -hex 20.

Verification (core)

Every request carries X-Pfy-Signature: sha256=<hex>. The value is the hex HMAC-SHA256 of the raw request body, keyed with your secret as a UTF-8 string (not base64-decoded). Strip the sha256= prefix before comparing. Printify has no webhook SDK, so verify manually against the raw body (don't JSON.parse first).

const crypto = require('crypto');

function verifyPrintifySignature(rawBody, header, secret) {
  if (typeof header !== 'string' || !header.startsWith('sha256=')) return false;
  const received = header.slice('sha256='.length).trim();
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  } catch {
    return false; // different lengths
  }
}

In Express, read the body with express.raw({ type: '*/*' }) so any content type verifies. There is no timestamp in the signature, so there is no replay window; dedupe on the event id.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

The payload is { id, type, created_at, resource: { id, type, data } }. The event name is in type.

EventTriggered Whenresource.data contains
order:createdAn order was createdNothing
order:updatedAn order's status changedshop_id, status
order:line-item:canceledA single line item was canceledline_item_id, status, status_details
order:sent-to-productionAn order was sent to productionNothing
order:shipment:createdSome or all items were fulfilledshipped_at, carrier, skus
order:shipment:deliveredSome or all items were delivereddelivered_at, carrier, skus
product:createdA product was createdshop_id
product:updatedA product was updatedshop_id
product:publish:startedPublishing to the storefront beganaction, publish_details
product:deletedA product was deletedshop_id
shop:disconnectedA shop was disconnectedNothing
personalization-preview-task:processedA preview task reached a terminal statetask_id, status, mockups

For the full event reference, see Printify's webhook documentation.

Delivery Behavior

  • Respond 200 OK. On a 4xx or 5xx, Printify retries up to 3 times, then blocks the webhook for 1 hour; no deliveries are sent during the block.
  • That makes returning 400 for a bad signature costly if your secret is misconfigured. Test with the simulate endpoint first.

Environment Variables

PRINTIFY_WEBHOOK_SECRET=your_printify_webhook_secret   # the "secret" passed when creating the webhook

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →