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/shopifynpm install @restaq/shopifyyarn add @restaq/shopifybun add @restaq/shopifyimport { 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
],
});- In your Shopify app or admin, subscribe to the topics you need and point the address at
/api/webhook/shopifyon your deployed app. - 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
| Option | Type | Default | Description |
|---|---|---|---|
| webhookSecret | string | process.env.SHOPIFY_WEBHOOK_SECRET | Your 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:
- Computes HMAC-SHA256 of the raw request body with your webhook secret, base64-encoded
- Compares it against the
X-Shopify-Hmac-Sha256header 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.