← API Explorer

Test webhook delivery

Sent from api-docs server (no CORS) — same as production dispatcher

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-Event and dispatches correctly
  • Verifies the X-Mfx-Signature HMAC (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)