How it works
A capture endpoint is a URL that accepts any request and stores it. Your test points the code under test at that URL, makes it send, then reads back what arrived:- Configure the sender to use the capture URL instead of the real receiver.
- Trigger the action that should send a webhook: place an order, change a status, run a job.
- Read the captured request: method, path, query, headers, and raw body.
- Assert on the parts that matter: the event type, the payload, the signature header.
500 or a slow response is how you test the sender’s retries and timeouts.
Three things called “webhook”
The word covers traffic in both directions. In the DevHelm docs:
An inbox is a testing tool. An HTTP monitor checks your receiver from outside on a schedule. An alert webhook is DevHelm sending events out.
Getting the request into the test
DevHelm inboxes use a server-side wait. It returns the oldest stored request that matches, as soon as it exists, or
408 WAIT_TIMEOUT when the time runs out. DevHelm does not forward or replay requests to another URL.
Record the start time first
A wait has to know which requests count. It matches requests received at or after a timestamp you pass. Take that timestamp before the trigger:Proving nothing was sent
“No webhook is sent for a failed payment” is a wait that is expected to time out. Its timeout has to cover the sender’s normal latency plus the few seconds the inbox takes to store a request. Too short, and the test passes before a late request lands.One inbox per test
A shared capture URL is the most common source of wrong matches: two tests, or two CI jobs, send to the same inbox, and one reads the other’s request. Isolate by test:- One inbox per test is the simplest. Create it at the start, delete it at the end.
- One inbox per CI job, one path per test fits a plan’s inbox cap. The inbox accepts any path below its token, so each test sends to
/<run-id>/<test-id>and waits with that path as a prefix.
Signatures
Most providers sign each webhook so the receiver can tell it is genuine. Stripe sendsStripe-Signature, GitHub sends X-Hub-Signature-256, and the Standard Webhooks spec uses webhook-signature. Each is an HMAC over the exact bytes of the body.
That makes signature tests fragile in one specific way: any step that parses and re-serializes the body changes whitespace or key order, and the HMAC no longer matches. Verify against the raw bytes, never against a parsed or decoded copy.
A DevHelm inbox stores the raw body byte for byte, with its SHA-256, and keeps every header. Header names arrive lowercased, so look up stripe-signature, not Stripe-Signature. There is no built-in verify call; you run the same check your receiver runs. Verify webhook signatures has the code for each scheme.
Testing failure handling
Receivers fail, and senders are supposed to retry. To test that, the capture endpoint has to fail on purpose: return a500, a 429, or a slow reply, then let the sender retry and check the attempts it made. A DevHelm inbox has a configurable reply (status, body, headers, and a delay of up to 30 seconds) for this. See Simulate a failing receiver.
DevHelm webhook inboxes
Your first webhook inbox
Create an inbox, send it a request, and assert on it in a test.
Webhook inboxes
What an inbox stores, mock replies, and every way to read events.