Prerequisites
Prerequisites
- The CLI (
npm install -g devhelm) or curl, plusjq, for the steps;@devhelm/sdkwith Vitest (ESM only), ordevhelmwith pytest and httpx, for the test - A free inbox slot. Inboxes are counted per organization: 3 on the Free testing plan, 10 on Developer
DEVHELM_WORKSPACE_ID and takes the key in its constructor. With curl, send the workspace as a header:Capture a request
1
Create an inbox
https://api.devhelm.io/api/v1/ingest/<public-token>. It accepts any method and any path below the token, with no auth. The token is the secret: anyone with the URL can send to the inbox.2
Record the start time, then send
SINCE before anything sends: wait returns the oldest request received at or after it. Without it, the CLI and SDKs use the clock at the moment you call wait, and a request that already arrived is missed.The curl stands in for your service. The inbox replies 200 with an empty body plus X-Event-Id and X-Inbox-Id. Storage is asynchronous, so getting the event by X-Event-Id can return 404 for about a second. Wait handles that.3
Wait and assert
timeoutMs (default 30000), the API returns 408 with code WAIT_TIMEOUT. REST returns {"event": {...}} with no data wrapper; the CLI prints the event object. jq -e exits non-zero when the expression is false, so a CI step fails like an assertion.The event also carries query (repeated keys kept), headers (names lowercased, nothing redacted), sha256 of the raw body, sourceIp, and receivedAt. Both SDKs parse body with event.json(). Signature checks hash the raw bytes instead; see Verify webhook signatures.4
Delete the inbox
Write the test
The same flow as one test file, with cleanup. Thefetch or httpx.post call stands in for the code under test: replace it with the call that makes your service send its webhook.
npx vitest run first-inbox.test.ts or pytest test_first_inbox.py. One test passes.
The runner timeout (45 s) is longer than the wait timeout (30 s), so a missing request fails with the API’s 408 instead of the runner killing the test. The test owns its inbox, so no earlier request can match. In CI, use one inbox per job and a path per test; see Testing in CI.
Troubleshooting
The wait returns 408 WAIT_TIMEOUT
The wait returns 408 WAIT_TIMEOUT
sincewas taken after the send, or not passed. Take it before the trigger.- The request path does not start with the
pathPrefixyou passed. The match is case-sensitive. - The sender failed. Check the status it got back.
404 Workspace doesn't exist, or 400 WORKSPACE_REQUIRED
404 Workspace doesn't exist, or 400 WORKSPACE_REQUIRED
The CLI and SDKs fall back to workspace
1 when DEVHELM_WORKSPACE_ID is unset, which gives the 404. A curl call without the x-phelm-workspace-id header gets the 400. Set both from the setup step.403 INBOUND_WEBHOOK_INBOX_LIMIT_REACHED
403 INBOUND_WEBHOOK_INBOX_LIMIT_REACHED
Your organization has used all its inbox slots. Find unused ones with
devhelm inboxes list and delete them. See Limits, quotas, and errors.Next steps
Webhook inboxes
Stored fields, mock replies, listing, and raw downloads.
Wait for a webhook in a test
The Nth request, negative assertions, and per-test paths.
What is webhook testing?
Capture vs polling vs tunnels, and why one inbox per test.