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 will 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 will have a src/index.ts to edit.

Step 2 — Add a KV namespace for idempotency

Shoppex assigns a unique delivery_id to each delivery. Manually retrying a webhook can create duplicates, so record the IDs you have already processed. If you see the same ID twice, short-circuit and skip reprocessing.
Wrangler prints a binding snippet — copy it into wrangler.toml:

Step 3 — Add the webhook secret

The webhook secret is what you will use to verify HMAC signatures. Add it as a wrangler secret (not in code, not in wrangler.toml):
You will 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 for missing headers. Then send full headers with a wrong signature to confirm it returns 401. That is 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 through 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” button on the webhook configuration page. Check your worker logs:
You will see the event come in, get verified, and be processed.

Event reference

Common events you will 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 status or times out (30s limit), Shoppex retries automatically. It uses an exponential backoff of 2 min, 4 min, 8 min, 16 min between tries. After 5 total attempts (the first call plus 4 retries) the delivery is marked failed. You can manually re-queue any failed delivery from the dashboard or through POST /dev/v1/webhooks/logs/:id/retry. The same delivery_id is reused. That is why idempotency (the KV check above) matters. Practical implications:
  • Your worker must 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 uses all 5 attempts, it is done. Shoppex will not try again. Monitor your worker logs (Cloudflare Analytics or wrangler tail) and the Shoppex webhook-logs dashboard for failed entries. Once you have fixed the root cause, manually re-queue those entries.

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 can 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.