v1.0 · Auto re-signing now live

Stop disabling signature verification
to debug webhooks.

Most replay tools break HMAC checks. Splithook re-signs every Stripe, Shopify, or GitHub payload with fresh timestamps — your code runs unmodified.

✓ Free plan forever ✓ Stripe · GitHub · Shopify · Twilio · Svix · Generic HMAC
splithook.com / dashboard / endpoints / demo1234
LIVE
streaming · 142 req/min
body headers raw ✓ signed · stripe
Fan-out · 3 destinations
9 618+
webhooks this week
5
providers re-signed
<50ms
p99 capture latency
99.9%
ingest uptime

The problem

Three steps to a production bug
that tests never catch.

1
seems fine

You skip verification because replays fail.

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);
2
invisible debt

Tests pass without ever verifying a signature.

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
3
prod breaks

Production rejects the payload you never tested.

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

From your provider to your localhost,
signature intact.

Provider Stripe / GitHub / … Splithook Capture in <50ms Re-sign engine HMAC + timestamp Your localhost Verifier passes ✓
POST /e/{slug}
persist body · async dispatch
strip → swap t= → recompute v1
your handler verifies normally

Auto-detection on capture · Re-signing on replay

Stripe
GitHub
Shopify
Twilio
AWS SNS
Mailgun
SendGrid
Vercel
Linear
Resend
Stripe
GitHub
Shopify
Twilio
AWS SNS
Mailgun
SendGrid
Vercel
Linear
Resend

Use cases

One tool, every webhook scenario.

Multi-site relay

One Stripe account.
Three independent sites.

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.

1 webhook URL in Stripe dashboard · 3 destinations · 0 extra Stripe configs

Local dev tunnel

External event. Localhost.
Signature valid.

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.

● orange = AWS Lambda event  ·  ● green = Shopify order  ·  ● green darker = re-signed, crossing NAT

Features

Every tool your webhook workflow needs.

01 / Auto re-signing

Signatures that actually
pass in dev.

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.

POST /hooks/demo · replay #3
Content-Type: application/json
Stripe-Signature: t=1714000001,v1=a3f9…8c2d
{
  "id": "evt_1Pq…",
  "type": "payment_intent.succeeded"
}

02 / Fan-out & filters

One event,
every destination.

Add destinations with optional per-destination filters (event type, expression language). Works for localhost ports, external APIs, and cross-site relays.

localhost:3000
payment.*
api.shop1.com
all events
api.shop2.com
orders/*

03 / Replay

Replay to localhost

One click. Timestamp swapped so 5-minute windows still pass. Original body bytes preserved.

# destination
http://localhost:3000/webhooks
✓ 200 OK · 12ms

04 / Inspect

Split-screen inspector

Payload, headers, response side-by-side. Visual diff across replay iterations.

request body
response

05 / Capture

Persistent capture

URL that never changes. Sub-50ms ingestion. Body and headers stored intact for 24h–90d.

streaming · auto-detect provider
Without Splithook
if (env === 'development') {
  // signature won't match replay
  return handler(req.body);
}
verifyStripeSignature(req);
handler(req.body);

Bugs surface in production only.

With Splithook
// re-signed at replay time
// timestamp swapped to now()
verifyStripeSignature(req); // ✓
handler(req.body);

Same code path, dev and prod.

Dev / prod parity

Stop disabling signature verification in dev.

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.

  • Stripe tolerance window: timestamp rewritten so 5-min window passes
  • GitHub: sha256-hex recomputed over preserved body bytes
  • Shopify: base64 HMAC — not hex, not URL-encoded, exactly per spec
  • Twilio: SHA-1 of URL + sorted form fields, destination URL-aware

Honest comparison

Splithook vs the alternatives.

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

Start free. Upgrade when
you replay signed webhooks.

Cancel anytime. No credit card on Free.

Free
0€

Test drive re-signing. 10 signed replays/mo.

  • 1 endpoint · 1 destination
  • 24h retention
  • 500 events/mo
  • 10 re-signed replays/mo
  • No credit card
Developer
9€ /mo

Full re-signing, unlimited. Solo dev ready.

  • 5 endpoints · 3 destinations each
  • 7d retention
  • 10k events/mo
  • Unlimited re-signing (all providers)
  • Email support
Most popular
Pro
19€ /mo

Multi-site fan-out with filters.

  • 15 endpoints · 5 destinations each
  • 30d retention
  • 100k events/mo
  • Unlimited re-signing (all providers)
  • Fan-out · Filters · Multi-site
  • Priority support
Team
49€ /mo

Shared workspaces, 5 members included.

  • Unlimited endpoints · 10 destinations each
  • 90d retention
  • 500k events/mo
  • Unlimited re-signing (all providers)
  • 5 users
  • Priority support + onboarding

All plans include fan-out, inspection, and live streaming. · See full feature comparison

FAQ

Common questions, answered.

How is webhook re-signing safe? +
Your signing secret is encrypted at rest with libsodium (key derived from APP_SECRET). It never leaves the server, is never logged, and is only used to compute a fresh signature when you trigger a replay.
Does my handler verify signatures normally? +
Yes — that is the entire point. Splithook strips the original signature, swaps the timestamp where applicable, and re-signs with your real secret. Your verification middleware has zero changes vs. production.
Which providers are supported for re-signing? +
Stripe, GitHub, Shopify, Twilio, Svix and Generic HMAC. The detector recognises more (AWS SNS, Mailgun, SendGrid, Resend, Linear, Vercel) for inspection but only HMAC-shaped providers get re-signing.
How is Splithook different from ngrok? +
ngrok exposes localhost via a tunnel. Splithook captures, inspects, replays and re-signs — they are complementary. Many users run Splithook as the public capture URL and ngrok or cloudflared as the tunnel from Splithook to localhost.
Does Splithook store webhook bodies? +
In Redis with a per-plan TTL: 24h on Free, 7 days on Developer, 30 days on Pro, 90 days on Team. You can delete any captured webhook from the dashboard at any time.
What HMAC algorithms does Splithook support? +
HMAC-SHA256 for Stripe, GitHub (X-Hub-Signature-256), Shopify and most modern providers. HMAC-SHA1 for Twilio. The signature scheme — including timestamp prefixing, version tagging, and base64 vs hex encoding — is per-provider and handled automatically.
What happens when my localhost is offline? +
The webhook is captured and stored regardless — your provider gets its 200 OK from Splithook. When you bring localhost back up, click replay and the body arrives with a fresh, valid signature. No retries to chase, no duplicate events from the provider.
Can I use Splithook in CI/CD pipelines? +
Yes. Capture a representative webhook once, then replay it from your test suite by calling the Splithook API. This gives you reproducible end-to-end tests against real provider payloads with valid signatures, without depending on the provider sending a fresh event during CI.
How fast is the capture-to-localhost path? +
Capture latency stays under 50ms p99 even with the worker chain dispatching downstream. The live timeline updates over Mercure SSE typically within 100ms of capture.

Your verification middleware deserves real signatures.

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 ''