Most replay tools break HMAC checks. Splithook re-signs every Stripe, Shopify, or GitHub payload with fresh timestamps — your code runs unmodified.
The problem
Provider signatures expire within minutes. When you replay a captured webhook in dev, the HMAC never matches — so the quickest fix is to skip it.
if (process.env.NODE_ENV === 'development') { return handler(req.body); // skip signature check } verifyStripeSignature(req); handler(req.body);
The test suite sends raw JSON with no signature header. The handler processes it because the dev-skip branch is active — and CI stays green.
# tests/test_webhook.py def test_payment_webhook(): response = client.post( "/webhooks/stripe", json=payload, # ← no Stripe-Signature header ) assert response.status_code == 200 # ✓ passes
A refactor changes how the body is parsed — req.body
is now JSON instead of raw bytes. The signature computation breaks silently.
Dev doesn't catch it. Production does.
// production — Friday 18:47 stripe.webhooks.constructEvent( req.body, // ← parsed JSON object, not raw Buffer sig, secret, ); // SignatureVerificationError: No signatures found // matching the expected signature for payload
The fix isn't better discipline — it's a replay that carries a valid signature in the first place.
The webhook journey
Architecture
Events from any source hit Splithook, get re-signed, and cross the NAT barrier
to your local services — no manual signature bypass, no NODE_ENV hacks.
Auto-detection on capture · Re-signing on replay
Use cases
Multi-site relay
Configure Splithook as your single Stripe webhook URL. It re-signs the event and fans it out to your DB and every site's API — simultaneously.
Local dev tunnel
AWS Lambda or Stripe fires to your Splithook URL.
Splithook re-signs and forwards through NAT to your local services —
no manual signature bypass, no NODE_ENV hacks.
Features
01 / Auto re-signing
Signing secrets encrypted at rest with libsodium. On every replay the HMAC is re-computed against the stored body bytes — identical code path to production.
02 / Fan-out & filters
Add destinations with optional per-destination filters (event type, expression language). Works for localhost ports, external APIs, and cross-site relays.
03 / Replay
One click. Timestamp swapped so 5-minute windows still pass. Original body bytes preserved.
# destination http://localhost:3000/webhooks ✓ 200 OK · 12ms
04 / Inspect
Payload, headers, response side-by-side. Visual diff across replay iterations.
05 / Capture
URL that never changes. Sub-50ms ingestion. Body and headers stored intact for 24h–90d.
if (env === 'development') { // signature won't match replay return handler(req.body); } verifyStripeSignature(req); handler(req.body);
Bugs surface in production only.
// re-signed at replay time // timestamp swapped to now() verifyStripeSignature(req); // ✓ handler(req.body);
Same code path, dev and prod.
Dev / prod parity
Every webhook replay carries a fresh HMAC — timestamp rewritten, signature
recomputed with your real secret. Your verification middleware runs the exact
same code path in dev as in production.
No NODE_ENV branches.
No if dev: skip.
Bugs that only appear in production become bugs you catch in dev.
Honest comparison
Each tool exists for a reason — most are great at one job. Splithook is the only one that re-signs replays with your real provider secret, which is the difference between a debugging session that mirrors production and one that doesn't.
| Capability | Splithook | ngrok | smee.io | webhook.site | Hookdeck |
|---|---|---|---|---|---|
| Public capture URL | — | ||||
| Replay to localhost | — | — | |||
| HMAC re-signing | — | — | — | — | |
| Multi-destination fan-out | — | — | — | ||
| Tunnel to localhost | — | — | — | ||
| Provider-aware inspection | — | — | — | ||
| Built-in retention & replay history | — | — |
Why re-signing matters: any tool that captures and replays without re-signing hands your handler a body whose signature was computed against the original timestamp. Once that timestamp is older than the provider's tolerance window (5 min for Stripe), your middleware rejects it as a replay attack. Splithook regenerates the timestamp and HMAC every time.
Combine, don't replace: Splithook + ngrok is a common setup. Use Splithook as the public capture URL and ngrok as the tunnel to localhost. Stable URL on the provider side, fresh tunnel every dev session, zero signature pain.
Pricing
Cancel anytime. No credit card on Free.
Test drive re-signing. 10 signed replays/mo.
Full re-signing, unlimited. Solo dev ready.
Multi-site fan-out with filters.
Shared workspaces, 5 members included.
All plans include fan-out, inspection, and live streaming. · See full feature comparison
FAQ
Sign up in 30 seconds, paste your endpoint URL into Stripe, and replay with valid HMAC
— no code changes, no if-dev-skip hacks.
$ curl https://splithook.com/e/your-endpoint -X POST -d ''