webhook.site earned its place: it hands you a URL instantly, no account, and every request that hits it shows up in the browser. That is exactly what you need at 2 a.m. when a provider refuses to deliver and you want to see what it sends. It is also, by design, a public endpoint: the URL is the only secret, and anyone who has it sees every payload that arrives — which is why teams eventually look for an alternative.
What people actually want from a catcher
- A URL that is not a public bucket. Payloads here contain real tokens, customer emails, signed links and sometimes personal data.
- Revocation. When the debugging session ends, the URL should stop accepting traffic.
- History, not just the last request. Retries from a provider are the interesting part: comparing attempt 1 with attempt 4 explains most integration bugs.
- Read-only sharing. A teammate should be able to watch the traffic without holding the account.
- Machine-readable output. A script (or an AI assistant) should be able to read the captured request without screenshots.
- A clear answer to “where does this live, and for how long?” — especially if your test payloads are close to production data.
The options
| Option | What it is | Trade-off |
|---|---|---|
| webhook.site | hosted catcher, URL without an account | the URL is the only credential; requests live on their servers; the free tier has limits and URLs can expire |
| RequestBin-style services (for example Pipedream) | hosted catcher with a dashboard and an account | accounts and limits; still a third party holding the payloads |
| A tunnel (ngrok, cloudflared) | exposes your local server to the internet | it is a transport, not a catcher: no history, no sharing, and the URL usually changes |
| Production webhook infrastructure (Hookdeck and friends) | delivery, retries and queues for real traffic | it is built for production webhooks, not for reading one payload by hand |
| This instance | hosted private catcher with accounts, share links and a JSON export | not self-hostable, and it is not free of accounts (one user per developer) |
| Your own endpoint | 30 lines that log to disk | full control, and full ownership of uptime, retention and access |
How this instance answers the checklist
| Concern | What happens here |
|---|---|
| Who can see the payload | only accounts of this instance; a URL is secret, and access is authenticated |
| Revocation | the URL can be deleted, and shared links expire and can be revoked at any time |
| Indexing | every capture and share link answers noindex, nofollow, noarchive, so a payload with a real token never ends up in a search index |
| History | every request is kept (with the retention window configured on the instance), retries included — compare attempt 1 with attempt 4 |
| Sharing | read-only links with an expiry, revocable, no account needed for the reader |
| Machine-readable | a share link answers as JSON or plain text, so scripts and AI assistants can read it |
| Email too | the same address accepts SMTP, so mail and callbacks are in one place |
If self-hosting is a hard requirement
A catcher is a small service, and writing your own is a legitimate option: accept any method, store method, path, headers, body and time, render a page, protect it with a token. What you then own is the boring part — uptime, retention, disk space and a way to revoke access that is not "delete the container". Run your own if the payloads must never leave your infrastructure; use a hosted catcher if the point is not to maintain anything. The two requirements contradict each other, so decide which one you actually have.
Moving off a public catcher in four steps
- Create a catcher URL in the tool you picked, and treat its token as a credential rather than a link to paste into chat.
- Register the new URL with the provider (Stripe, GitHub, Telegram …) and trigger one event.
- Confirm the request arrived whole — method, path, query, headers, body and client IP. A capture missing the body usually means the provider sent a content type you did not expect.
- Delete the old endpoint after the first successful delivery, and if the old URL was shared anywhere, assume whatever it captured has been read.
If you chose the public service because it needed no account, that is the one thing you give up here — and in exchange the URL stops being readable by whoever happens to find it.
Next steps
- How to test webhooks locally without ngrok — the four approaches compared, with per-provider steps.
- Mailtrap alternatives: free, open-source and self-hosted options — the same reasoning applied to email sandboxes.
- Privacy and your data — what this instance stores, for how long, and how to delete it.
- Limits of this instance — request size, retention and quotas in one table.