Shopify

Updated May 02, 2026

Configure the webhook in Shopify

Shopify webhooks can be registered via the Admin UI or the Admin API.

Admin UI

  1. Shopify Admin → Settings → Notifications → scroll to Webhooks.
  2. Create webhook.
  3. Event: pick the topic (e.g. orders/paid).
  4. Format: JSON.
  5. URL: https://splithook.com/e/{slug}.
  6. Save.

The webhook secret is shown after creation — copy it immediately.

Admin API

curl -X POST https://{shop}.myshopify.com/admin/api/2024-04/webhooks.json \
  -H "X-Shopify-Access-Token: {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook": {
      "topic": "orders/paid",
      "address": "https://splithook.com/e/{slug}",
      "format": "json"
    }
  }'

The shared secret is your app's client secret (not the access token).

Add the signing secret to Splithook

  1. Settings → Signing secrets → New.
  2. Provider: Shopify.
  3. Paste the webhook secret (shown in Admin → Notifications → Webhooks).
  4. In your destination, set Signing mode to Re-sign, select this secret.

Verify signatures in your handler

// Node.js
import crypto from 'crypto';

function verifyShopifyWebhook(rawBody: Buffer, hmacHeader: string, secret: string): boolean {
  const computed = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('base64');
  return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(hmacHeader));
}

app.post('/webhooks/shopify', express.raw({ type: 'application/json' }), (req, res) => {
  const hmac = req.headers['x-shopify-hmac-sha256'] as string;
  if (!verifyShopifyWebhook(req.body, hmac, process.env.SHOPIFY_WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }
  // handle event...
  res.sendStatus(200);
});

How Splithook re-signs Shopify events

Shopify's signature format:

x-shopify-hmac-sha256: abcABC123==

The value is base64(HMAC-SHA256(secret, body)). No timestamp. On replay, Splithook recomputes the HMAC and base64-encodes it.

Filtering by topic

Shopify sends the topic in X-Shopify-Topic (mapped to event_type):

event_type == 'orders/paid'
event_type == 'orders/cancelled'
event_type matches '^orders/'
event_type in ['products/create', 'products/update']