We sent webhookjar.com tens of thousands of real webhooks through Cloudflare, the way Stripe and GitHub do, then counted every one in the database. Here is what it does, how fast, and where it stops.
A sender's request crosses Cloudflare, lands on our box, is read, checked and saved in one step, and answered. Timings are medians from the last run.
DNS, a connection and TLS to Cloudflare's edge nearest the sender. A sender that keeps its connection open skips this next time.
The request travels from the edge to our server and the answer travels back. This is most of what a sender waits for.
Who sent it, the event, repeats, signatures, a live key inside. Then the counter and the save happen in one write, so nothing is ever half stored.
The sender gets its 200 at once. Your inbox shows it live, the API hands it to code that waits, and forwarding runs after the answer.
Each call sent alone and timed from the sender's side, through Cloudflare. p95 means 95 in 100 were at least this fast.
More and more senders at once, 15 seconds each, until answers got slow. Spread over 2,500 inboxes, half anonymous and half a member's, so no per-inbox limit got in the way.
Past 50 senders at once, requests queue for their turn to be saved. They still land; they just wait. If one ever waits over 5 seconds, the sender is told to send again, and every provider does.
The test gives every webhook its own event ID and counts them in the database afterwards, not just the answers.
The extra one is a webhook whose answer never reached the sender because the connection dropped. We had saved it, which is the right way round: a sender that hears nothing sends again, and the copy is marked as a repeat, so a handler that stores event IDs skips it.
12 sent twice the same way: the tester's own connection dropped and it resent. Both copies kept, the second one flagged.
Each one was pushed past its edge and counted on the server, not guessed from the outside.
Exactly 120 kept, the next 30 told to slow down.
Exactly 300 let through on Solo, the next told when to try again. Free has 30.
The last one of the day is kept, the next is refused until midnight UTC. Free has 5,000.
Request 500 lands, 501 is told the inbox is full.
A 1.44 MB body was answered 200 and its first 1 MB kept, with a note on the request.
Enough for a test suite that opens one per run; the next is refused for the hour.
The point of testing is finding things. Each of these was found by the load test and fixed the same night, then tested again.
What the test does on every run, grouped as it runs.
Headers, body and our checks together. About 880,000 of them fit in a gigabyte.
7 days on Free, 48 hours without an account. Older requests are cleared every 10 minutes.
Deleting an inbox deletes its requests at once, and the space is given back.
From our own server, out to Cloudflare and back in, the same path every sender takes. A real provider sends from its own network, so it pays its own distance to Cloudflare on top.
Runs vary with Cloudflare and the network from minute to minute: the best steady level moved by about a fifth between runs on the same night. So we quote a range and plan on the low end.
A floor. The sending side ran on the same box and used some of its CPU, and all the traffic came from one address. Real senders are spread out.
Before every change that touches how webhooks are saved. This page reads the latest results, so it is never older than the code.
Open an inbox and point your provider at it. It is answered before you have finished reading this sentence.