Restaq
Plugins

Shopify Plugin

Receive and verify Shopify webhooks with Restaq.

The @restaq/shopify plugin verifies Shopify webhook HMAC signatures and normalizes Shopify topics into Restaq event names.

Installation

pnpm add @restaq/shopify
import { shopify } from '@restaq/shopify';

Setup

// relay.ts
import { restaq as createRestaq } from 'restaq';
import { shopify } from '@restaq/shopify';

export const restaq = createRestaq({
  plugins: [
    shopify(), // reads SHOPIFY_WEBHOOK_SECRET automatically
  ],
});
  1. In your Shopify app or admin, subscribe to the topics you need and point the address at /api/webhook/shopify on your deployed app.
  2. Set your app's Client secret (or the webhook-specific secret, depending on how you subscribed) as SHOPIFY_WEBHOOK_SECRET.

Pass webhookSecret explicitly instead if you'd rather not rely on the env var:

shopify({ webhookSecret: 'shpss_...' }),

Configuration

OptionTypeDefaultDescription
webhookSecretstringprocess.env.SHOPIFY_WEBHOOK_SECRETYour app's client secret, used to verify deliveries

Signature verification

The plugin verifies the X-Shopify-Hmac-Sha256 header, per Shopify's own webhook verification guide:

  1. Computes HMAC-SHA256 of the raw request body with your webhook secret, base64-encoded
  2. Compares it against the X-Shopify-Hmac-Sha256 header using constant-time comparison

Unlike Stripe/Clerk/Resend, Shopify's scheme has no timestamp — it's a plain HMAC of the body, so there's no toleranceSeconds option.

Event types

Shopify topics arrive slash-separated (e.g. orders/create) and are converted to dot-separated Restaq event names, namespaced under shopify.:

// relay.handlers.ts
import type { AppRelay } from './relay';

export function registerHandlers(relay: AppRelay): void {
  relay.on('shopify.orders.create', async (event, ctx) => {
    ctx.log.info('order created', { id: event.data.id });
  });

  relay.on('shopify.app.uninstalled', async (event, ctx) => {
    ctx.log.info('app uninstalled', { shopId: event.data.id });
  });
}

See the webhooks reference for the full list of supported topics and their payloads.

Deduplication

Every Shopify webhook includes an X-Shopify-Webhook-Id header that uniquely identifies the delivery. The plugin uses it as the Restaq event ID, so a redelivered webhook collapses into the same execution instead of running your handler twice.

Testing locally

Use relay trigger to simulate a signed Shopify webhook:

relay trigger shopify orders.create --data '{"id":123}'

Make sure SHOPIFY_WEBHOOK_SECRET is set to the same value on both relay dev and relay trigger, or unset on both — see the Local Development gotcha for why.

On this page