Skip to main content
Cloudflare Workers are a good fit for webhook handlers — cheap, fast, globally distributed, no servers to babysit. This tutorial wires up a worker that receives Shoppex webhook deliveries, verifies the signature, deduplicates by delivery ID, and reacts to the event.

What you’ll have at the end

  • A deployed Cloudflare Worker that listens for Shoppex events.
  • HMAC-SHA256 signature verification against the V2 header.
  • Idempotency using Cloudflare KV (so you can safely receive duplicates).
  • A registered Shoppex webhook pointing at the worker.

Prerequisites

  • A Cloudflare account with Workers enabled.
  • wrangler CLI installed (npm i -g wrangler or bun add -g wrangler).
  • A Shoppex shop with an API key that has webhooks.write.

Step 1 — Create the worker

Pick the “Hello World” Worker template, TypeScript. You’ll have a src/index.ts to edit.

Step 2 — Add a KV namespace for idempotency

Shoppex assigns a unique delivery_id to each delivery. To safely handle duplicates (which can happen if you manually retry a webhook), record the IDs you’ve already processed and short-circuit if you see one twice.
Wrangler prints a binding snippet — copy it into wrangler.toml:

Step 3 — Add the webhook secret

The webhook secret is what you’ll use to verify HMAC signatures. Add it as a wrangler secret (not in code, not in wrangler.toml):
You’ll get the secret from Shoppex in step 8 (when you create the webhook), so come back here once.

Step 4 — Write the handler

Replace src/index.ts:

Step 5 — Test locally

Wrangler runs the worker at http://localhost:8787. Send a POST request with fake headers to confirm it returns 400 (missing headers) — then 401 (bad signature) when you send full headers but wrong signature. That’s the right behavior.

Step 6 — Deploy

Wrangler gives you a URL like https://shoppex-webhook-worker.your-account.workers.dev. Copy it.

Step 7 — Register the webhook in Shoppex

From your dashboard at Settings → Developer → Webhooks or via the API:
The response includes a secret. Save it immediately — Shoppex shows it only once.

Step 8 — Set the secret in the worker

Step 9 — Verify

Trigger a real event — make a purchase on your shop or use the dashboard’s “send test event” on the webhook configuration page. Check your worker logs:
You should see the event come in, get verified, and be processed.

Event reference

Common events you’ll likely subscribe to:
  • order:created — new order, not yet paid.
  • order:paid — payment confirmed. This is the event 90% of integrations care about.
  • order:cancelled, order:disputed — bad-path events worth knowing about.
  • subscription:renewed, subscription:cancelled — recurring billing lifecycle.
  • subscription:trial:started, :trial:ended — for nudge flows.
  • product:stock — direct catalog stock update. For stock consumed by checkout, use order:paid or order:paid:product.
Up to 12 events per webhook, and 15 webhooks per shop in total.

Retry behavior — important

If your worker returns a non-2xx or times out (30s limit), Shoppex automatically retries on an exponential backoff: 2 min, 4 min, 8 min, 16 min between attempts. After 5 total attempts (initial + 4 retries) the delivery is marked failed. You can manually re-queue any failed delivery from the dashboard or via POST /dev/v1/webhooks/logs/:id/retry. The same delivery_id is reused — that’s why idempotency (the KV check above) matters. Practical implications:
  • Your worker should be reliable. Use a Worker (which is globally distributed) rather than a single-region origin server. Transient failures are forgiven automatically; sustained outages still get the delivery through if you recover within the ~30-minute retry window.
  • Make handlers idempotent. Both automatic retries and your own manual retries will re-send the same delivery_id. The KV check above is what catches both cases.
  • Watch for failures. If a delivery exhausts all 5 attempts, it’s done — Shoppex won’t try again. Monitor your worker logs (Cloudflare Analytics or wrangler tail) and the Shoppex webhook-logs dashboard for failed entries you’ll need to manually re-queue once you’ve fixed the root cause.

Common pitfalls

  • Parsing the body before verifying. You must use request.text() to get the exact raw bytes Shoppex signed. If you request.json() first and re-serialize, the message no longer matches the signature.
  • Reading headers case-sensitively. The Fetch API in Workers gives you case-insensitive access, but be consistent.
  • Forgetting the timestamp window. Without a 5-minute timestamp check, an attacker who captured one signed payload could replay it forever.
  • Not enabling KV in production. Without idempotency, a manual retry double-processes the order. Worth the 5 minutes of KV setup.

Reference: Webhooks

Full event list, payload shapes, and the signature spec.