What this does
Sends a single demo webhook to your endpoint with the same headers and envelope MerchFox uses in production. Use it BEFORE you create a real subscription to verify your endpoint:
- Receives POST + parses the JSON body shape you expect
- Reads
X-Mfx-Eventand dispatches correctly - Verifies the
X-Mfx-SignatureHMAC (paste your secret below if you already have one) - Returns 2xx within your timeout (10s default in production)
This page does NOT touch MerchFox state — no subscription is created, no delivery is logged. The POST is performed by the api-docs server (Next.js route /api/test-webhook-proxy) so it arrives server-to-server, exactly like the production dispatcher does. No browser CORS preflight, no need for your endpoint to expose any CORS headers — the same setup that works in production works here.
1. Configure
Your service. For local testing use a tunnel like ngrok or cloudflared and paste the public URL.
12 event types. Pick one matching the integration path you want to exercise.
If provided, we compute X-Mfx-Signature exactly like production: sha256=hmac_sha256(secret, raw_body). Leave blank for a no-signature smoke test.
2. Demo payload
513 bytes{
"event_id": "evt_019fa2bea2fe43cb53c1eda5e627",
"event_type": "order.paid",
"occurred_at": "2026-07-27T08:43:50.398Z",
"api_version": "v1",
"seller_id": "user_01HXYZDEMOSELLER0001",
"resource_id": "order_01HXYZDEMOORDER0001",
"resource_type": "order",
"data": {
"order_id": "order_01HXYZDEMOORDER0001",
"status": "PAID",
"currency": "USD",
"subtotal": 24,
"shipping_total": 5,
"tax_amount": 1.92,
"total": 30.92,
"source": "STOREFRONT",
"items_count": 1
}
}Same shape MerchFox sends in production — only IDs and timestamps differ. Edit app/test-webhook/page.tsx if you want to customize fields per test.
3. Send it
Or run the same delivery via curl (useful for CI / scripted tests)
BODY=$(cat <<'JSON'
{
"event_id": "evt_019fa2bea2fe43cb53c1eda5e627",
"event_type": "order.paid",
"occurred_at": "2026-07-27T08:43:50.398Z",
"api_version": "v1",
"seller_id": "user_01HXYZDEMOSELLER0001",
"resource_id": "order_01HXYZDEMOORDER0001",
"resource_type": "order",
"data": {
"order_id": "order_01HXYZDEMOORDER0001",
"status": "PAID",
"currency": "USD",
"subtotal": 24,
"shipping_total": 5,
"tax_amount": 1.92,
"total": 30.92,
"source": "STOREFRONT",
"items_count": 1
}
}
JSON
)
SIG="sha256=demo-no-secret-provided"
curl -sS -i -X POST "https://webhook.site/your-uuid-here" \
-H "Content-Type: application/json" \
-H "User-Agent: MerchFox-Webhooks/1.0" \
-H "X-Mfx-Event: order.paid" \
-H "X-Mfx-Event-Id: evt_demo_$(date +%s)" \
-H "X-Mfx-Delivery-Id: dlv_demo_$(date +%s)" \
-H "X-Mfx-Subscription: wsub_DEMO_FROM_API_DOCS" \
-H "X-Mfx-Signature: $SIG" \
-H "X-Mfx-Api-Version: v1" \
-H "X-Mfx-Demo: true" \
-d "$BODY"Verify the signature on your side
Every production delivery includes X-Mfx-Signature: sha256=<hex>. Compute the same value over the RAW request body (before JSON parsing) and compare with constant-time equality.
// Node.js
const crypto = require('crypto');
const sig = req.headers['x-mfx-signature']; // "sha256=abc123..."
const expected = 'sha256=' + crypto
.createHmac('sha256', YOUR_WEBHOOK_SECRET)
.update(rawBody) // <-- raw bytes, NOT JSON.parse'd
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('bad signature');
}
// Python
import hmac, hashlib
expected = 'sha256=' + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(received_sig, expected):
abort(401)