Webhooks, made for humans and for machines.
A small tool to test webhooks. Catch one, change its payload, reply with any status, and watch what the sender does next. What takes a paid plan or a CLI elsewhere is one click here, or one curl for your scripts. Self-hosted, open source. When your handler runs on localhost, your browser tab is the tunnel.
- 1Go binary, UI built in, Apache 2.0
- ∞when self-hosted: your server, your limits
- JStransforms and scripted replies, free
- 0installs to reach localhost: your tab is the tunnel
No account: create an endpoint on the demo and point any service at its URL.
Then open localhost:8080 · No database needed. Linux, macOS, Windows; checks the SHA-256.
A local install is for testing: services on the internet can't reach localhost, so it only gets requests you send from your machine. To receive real webhooks, deploy it or use the live demo.
- Your localhost answers the sender
- Forward to localhost from the tab
- Standard Webhooks signature verification
- HMAC and Ed25519 signatures
- Reject bad signatures with 401
- JavaScript transforms, free
- Scripted replies, free
- Live diff before you save
- Try a script on recent traffic
- Request forwarding with retries
- Sync or async forwarding
- Replay any request
- Copy as cURL
- Export requests as JSON
- Envelope-encrypted secrets, write-only
- Browser mode: nothing on disk
- Self-hosted: your server, your limits
- In-memory mode, no database
- PostgreSQL when you want to keep it
- REST API and WebSocket
- One Go binary, UI embedded
- Self-host with scripting
- Rate limiting and trusted proxies
- Docker image and Vercel deploy
- Apache 2.0
Three things to try in your first minute.
Testing a webhook is mostly poking at it: change a field, fail on purpose, see what the sender does. Elsewhere that needs a paid plan, a CLI or a config file. Here each one is a click on the live demo, no account, and the same call from curl.
Change a payload and see the diff before it ships
Open a captured request, click Test a transform on this, type two lines of JavaScript. The diff updates as you type, it runs against your recent traffic, and nothing is saved until you say so.
How transforms workMake the sender retry, on purpose
Script a 500 reply and watch the provider's retry arrive. Then return 200. You just tested your error handling without touching your app, and it cost nothing.
How scripted replies workWatch your localhost answer the provider
Point forwarding at localhost, turn on the local reply. The provider gets your handler's real status and body, through your browser tab. No CLI, daemon or account.
Webhooks to localhostThe one-liners above run Testhooks on your machine, which is enough to try it with curl. Real providers can only deliver to a URL they can reach, so for those deploy it on a server or PaaS with a public URL.
Two modes. Pick your trade-off.
The server does the work. Always on.
Every request is stored, transformed and forwarded on the server. Your browser is only a viewer, so it keeps working with no tab open.
Stored on the server
In PostgreSQL, or in memory with an optional snapshot file saved every 30 s and on shutdown.
Headless transforms and forwarding
JavaScript transforms run on the server, then the request is forwarded to the configured URL.
Custom responses
The sender gets back the exact response you scripted.
Use it for shared or long-running instances, and anything that must keep working with no tab open.
Your browser is the tunnel. Both ways.
The server holds the provider's request open and streams it to your tab over WebSocket. Your tab runs the transform, delivers with fetch() to localhost, and sends your app's reply back. The provider sees that reply.
The server only relays
Transforms and forwarding run in your tab, which is how it reaches localhost, Docker and anything else your machine can see.
Your app's reply reaches the sender
Turn on Reply to the sender with my app's response. A 200 marks the delivery done; a 500 makes the provider retry, as in production.
Nothing stored unless you ask
Payloads never touch the server's disk. Turn on Persist requests to keep a copy.
Example: your receiver only runs on localhost
- 1Create an endpoint on the live demo or your own Testhooks and paste its /h/:slug URL into the provider's webhook settings.
- 2In the tab, set forward to http://localhost:[port]/[path] and turn on the local reply.
- 3Trigger the event. It lands in your tab and on your local server, and the provider gets your handler's reply.
Verify signatures like a real receiver.
Testhooks checks Standard Webhooks signatures from Sparrow, Svix and other senders that follow the spec. Turn on enforcement and bad signatures are rejected, so you test the same path production takes.
- Reads the spec headers: webhook-id webhook-timestamp webhook-signature
- Can reject invalid signatures with 401 instead of accepting everything
- Works with Sparrow, Svix and other compliant senders
Everything you need to test a webhook. Nothing you don't.
Not a webhook server, not a gateway: one Go binary with the UI embedded. Every screen is a REST call you can make with curl. Storage is PostgreSQL, or memory for local testing.
Replace the tunnel
Forward from your tab to localhost, Docker or anything your machine reaches, and the sender gets your app's reply. No CLI, daemon or account.
Private by default
In browser mode, payloads are relayed over WebSocket and never written to the server's disk.
Live inspection
Every request appears the instant it arrives: headers, query, body with syntax highlighting. Copy as cURL, export as JSON.
Script transforms
Reshape payloads with a few lines of JavaScript, sandboxed in WASM, before they are forwarded.
Forward and mock
Forward to any URL, sync or async with retries, and script the exact response the sender gets back.
Verify signatures
Check Standard Webhooks signatures from Sparrow, Svix and others, and reject bad ones like a real receiver.
Where it's different, and where it isn't.
All three catch a webhook and show it to you. The difference is what happens when you want to poke at it.
| Testhooks | webhook.site | ngrok | |
|---|---|---|---|
| Getting a URL | One click, no account | One click, no account | Install the agent, sign in |
| Changing a payload | Type JavaScript, watch the diff on real traffic, save when it's right | Custom Actions, on a paid plan; edit, then run | Traffic Policy rules in config |
| Scripting the reply | JavaScript, in the browser, free; preview what the sender gets | Static response free; WebhookScript on a paid plan | Policy rules; the app's own reply through the tunnel |
| Seeing what happened to one request | Received, transformed, forwarded, replied: each step with its result on the request | Request and your configured response | Request and response in the inspector |
| Reaching an app on localhost | From the tab; your app's reply goes back to the sender | From the tab (XHR Redirect), fire and forget; or a CLI | Through the agent, full round trip |
| Checking a signature | Standard Webhooks, marked on every request; secrets write-only, encrypted | Not built in | 50+ providers; metered on the free plan |
| Self-hosting | One binary with everything above | Open-source edition (MIT), without Custom Actions | Not offered |
| Limits on the hosted free tier | Demo: 500 requests or 10 MB per endpoint, URLs public; self-hosted: your limits | 100 requests, 7 days, URLs public; self-hosted: your limits | 20k requests a month, 3 endpoints, random URL |
From each product's public docs, October 2026: webhook.site's hosted free tier and open-source edition, ngrok's free plan. Paid plans add more on all sides. Corrections welcome on GitHub.
Point any webhook at it. See everything, reply with anything.
Try the live demo now, or run it locally to test with curl.