Local development
The problem with local webhook development
Your local server isn't reachable from the public internet. The usual workarounds all have friction:
- ngrok — free tier drops tunnels after 2h, paid tier still requires managing tokens and URLs that change on restart.
- Cloudflare Tunnel — powerful but heavyweight for a dev session.
- Manual curl — you lose the real payload, the real headers, and the real signing context.
Splithook tunnels solve all three: the endpoint URL is permanent, the CLI connects in seconds, and you receive the exact bytes the provider sent — headers, signature, and all.
How the tunnel works
Provider → splithook.com/e/{slug} → (Mercure channel) → splithook CLI → localhost:3000
- The provider POSTs to your Splithook endpoint as normal.
- Splithook captures the request and broadcasts it over a private Mercure event stream authenticated by a tunnel token.
- The CLI subscribes to that stream, receives the event, and replays it to your local port.
- Your handler's response (status, headers, body) is sent back to Splithook via an ACK endpoint and recorded in the ReplayLog.
The connection is outbound-only from the CLI — no port forwarding, no firewall changes.
Install the CLI
# macOS (Homebrew)
brew install splithook/tap/splithook
# Linux — curl installer
curl -fsSL https://splithook.com/install.sh | sh
# Or download the Phar directly
curl -Lo splithook https://splithook.com/cli/latest/splithook.phar
chmod +x splithook && sudo mv splithook /usr/local/bin/
Verify the install:
splithook --version
# splithook 1.0.0 (php 8.4)
Authenticate
splithook auth
This opens a browser window where you log in. The CLI stores a token in ~/.splithook/credentials.json.
You can also authenticate non-interactively using a tunnel token generated from the dashboard (Settings → Tunnel tokens → New):
splithook auth --token sht_xxxxxxxxxxxxxxxx
Start a tunnel
splithook tunnel --endpoint ab3dkf7z --to localhost:3000
Options:
| Flag | Default | Description |
|---|---|---|
--endpoint |
— | Your endpoint slug (required) |
--to |
— | Local address to forward to (required) |
--path-prefix |
(none) | Prepend a path to forwarded requests, e.g. /webhooks |
--timeout |
30s |
Max wait for local handler response |
The CLI prints a status line on each forwarded event:
→ POST charge.failed 200 142ms
→ POST customer.created 200 88ms
→ POST invoice.payment_failed 503 5001ms [retry scheduled]
Replay to tunnel
You can replay any captured webhook directly to your tunnel from the dashboard inspector. The payload is re-signed if you've configured a signing secret on the destination, so your local HMAC verifier keeps passing.
Multiple developers
Each developer creates their own tunnel destination on the same endpoint. Fan-out ensures everyone receives every webhook simultaneously — no coordination required.
Endpoint ab3dkf7z
├── Destination: alice-tunnel → alice's laptop:3001
└── Destination: bob-tunnel → bob's laptop:3002
Add a filter (event_type == 'payment_intent.succeeded') to each destination if you only care about specific events.