Skip to content

WEBHOOK TESTING · BETA

Don't sleep(10). Wait for the webhook.

A private URL that accepts any method on any path. Paste it into Stripe, inspect what arrives, and let your test block on one API call until the webhook lands, or fail with a 408.

REST API, plus TypeScript and Python SDKs.

Webhook debugger · Example workspace

WHAT IT DOES

Capture it. Inspect it. Wait for it.

The three jobs of a webhook test, each one an API call.

Capture.

Any method, any path. Method, path, query string, headers and body are stored for every request: Stripe, GitHub, Auth0, or a partner's "we'll send you a webhook".

Inspect.

Open a request to read the JSON body, every header and the query string. See whether the provider sent what your handler expects.

Wait.

One call blocks until a matching request arrives. It turns "Stripe should have called us" into an assertion.

THE WAIT API

Your test blocks until the provider actually fires

Trigger the action, then make one call. It returns as soon as a matching request is stored. No polling loop, no sleep(10).

  • One call: POST /api/v1/webhook/inboxes/{id}/wait with a timeoutMs. Default 30 s, maximum 120 s.
  • No trigger-then-wait race. A request that arrived just before you called wait still counts.
  • A failure your test can read. A timeout is HTTP 408 WAIT_TIMEOUT, so the test fails with a reason instead of an empty body.
  • Call it from any test runner or CI job with curl, fetch, or the TypeScript and Python SDKs.
import { test, expect } from '@playwright/test';

const API = 'https://api.devhelm.io/api/v1';

test('Stripe calls our webhook after checkout', async ({ page, request }) => {
  test.setTimeout(60_000);

  await page.goto('https://staging.acme.dev/pricing');
  await page.getByRole('button', { name: 'Start Pro' }).click();
  // ...complete checkout with Stripe's 4242 test card...

  const res = await request.post(`${API}/webhook/inboxes/${process.env.INBOX_ID}/wait`, {
    headers: {
      Authorization: `Bearer ${process.env.DEVHELM_API_TOKEN}`,
      'x-phelm-workspace-id': process.env.DEVHELM_WORKSPACE_ID!,
    },
    data: { timeoutMs: 30_000, http: { method: 'POST', pathPrefix: '/stripe' } },
    timeout: 35_000,
  });
  expect(res.status(), 'Stripe never called the webhook').toBe(200);
});
200 · matching request returned408 · WAIT_TIMEOUT after 30 s

PRIVATE BY DEFAULT

Not a link in someone's bookmarks. A URL your team owns.

The URL you paste into Stripe is public, because providers cannot send a login. Reading what arrived is not: that needs your workspace.

  • Inboxes belong to the workspace, so the URL outlives whoever created it.
  • A leaked URL lets someone send to the inbox. It never shows them what arrived.
  • Give each test or environment its own inbox, so parallel CI jobs never wait on each other's events.

Public sender

POST the ingest URL

Capture URL

stripe-staging

Private workspace

Authenticated read

Anyone with the ingest URL can send. Workspace access controls who can read.

CUSTOM REPLIES

The sender gets the answer you configured

Set the status code, content type and body your inbox returns, and hold the reply for up to 30 seconds, so you can see how a provider behaves when your endpoint says 200, 410 or 500, or answers slowly.

  • Any status from 200 to 599, any content type, your own body. Extra headers through the API.
  • Return 500 and watch the provider's retry schedule play out in the request list.
  • Out of the box the inbox replies 200 with an empty body.
Webhook debugger · Example workspace

SETUP

From API key to first captured request

Once the DevHelm team turns on your workspace, three steps get the inbox ready for your test suite.

  1. 1

    Create an API key.

    In the dashboard. Your tests send it with your workspace ID; it is the only credential they need.

  2. 2

    Create an inbox.

    In the dashboard or through the API. You get a URL to paste into your provider.

  3. 3

    Send a request.

    Click "Send test webhook" in Stripe, or run curl -X POST <your-inbox-url>/stripe -d '{"ping":true}'. It shows up in the inspector and in your first wait call.

$ curl -sS -o /dev/null -w '%{http_code}\n' -X POST "$INBOX_URL/stripe" -d '{"ping":true}'
200

FIT CHECK

Is this the right webhook tool?

It is a test inbox with a wait call. If that is the job, it is built for you.

Strong fit when

  • You are wiring a Stripe, GitHub, Paddle or Auth0 webhook this afternoon and want a URL that is yours.
  • Your CI has a sleep(10) waiting on a provider, and it flakes.
  • Your team shares a personal throwaway link and nobody knows who owns it.

Consider another option when

  • You need a production event gateway with retries, fan-out or transformations. This does not forward events to your servers.
  • You want a URL in seconds with no account. Every inbox lives in a DevHelm workspace.
  • You want a provider to reach a handler on localhost. That is a tunnel's job; this is the inbox your test waits on.

PRICING

A free plan. Paid plans from $19 a month.

Testing is its own plan, on the same subscription as monitoring. Add it to any monitoring plan, Free included.

Free

$0

  • 3 webhook inboxes
  • 1 email domain
  • 300 stored events / mo
  • 3-day event history

Developer

$19 / mo

  • 10 webhook inboxes
  • 1 email domain
  • 3,000 stored events / mo
  • 7-day event history

Team

$79 / mo

  • 100 webhook inboxes
  • 3 email domains
  • Your own domain (custom MX)
  • 100,000 stored events / mo
  • 30-day event history

Business

$149 / mo

  • 500 webhook inboxes
  • 10 email domains
  • Your own domain (custom MX)
  • 300,000 stored events / mo
  • 90-day event history

Enterprise: unlimited inboxes, domains and events, 365-day history, custom pricing. Stored events count webhook and email together. Annual billing: two months free.

FAQ

Frequently asked questions

Waiting, privacy, Stripe and pricing.

What is a webhook tester?

A webhook tester gives you a URL that accepts HTTP requests and shows you what arrived, so you can see a provider's payload before your own handler exists. DevHelm's is private to your workspace, adds a wait API for CI, and lets you set the reply.

How do I test a webhook endpoint in CI without sleeping?

Trigger the action, then call POST /api/v1/webhook/inboxes/{id}/wait with a timeoutMs (default 30 s, maximum 120 s). It returns the request as soon as it is stored, or a 408 if none arrives. Your test asserts on what came back.

What happens if the webhook never arrives?

The wait call returns HTTP 408 with WAIT_TIMEOUT after your timeout. It never returns an empty 200, so a missing webhook fails your test with a clear reason.

Is my inbox URL private?

Anyone with the URL can send to it, because providers can't sign in. Listing and reading events needs your DevHelm login or API key.

Can I control what the sender receives?

Yes. Set the status (200 to 599), content type, body and a reply delay of up to 30 seconds for each inbox, for example a 500 to see how a provider retries. Extra headers can be set through the API.

Can I test Stripe webhooks without ngrok?

Yes for receiving them: paste the inbox URL into the Stripe dashboard and send a test event. No tunnel needed because the inbox is a public URL. If you need Stripe to reach your local handler, you still need a tunnel for that.

Is it free? What does it cost?

There is a free plan: 3 inboxes, 300 stored events a month and 3 days of history. Paid plans are $19, $79 and $149 per month, and annual billing gets two months free.

Delete the sleep. Wait for the webhook.

The beta is open by invitation. Book a 30-minute demo and we'll set up your workspace.

$ curl -sSf -X POST https://api.devhelm.io/api/v1/webhook/inboxes/$INBOX_ID/wait \
    -H "Authorization: Bearer $DEVHELM_API_TOKEN" -H "x-phelm-workspace-id: $DEVHELM_WORKSPACE_ID" \
    -H "Content-Type: application/json" \
    -d '{"timeoutMs":30000,"http":{"method":"POST","pathPrefix":"/stripe"}}'
✓ 200 · POST /stripe returned

Also testing signup email? See Email Testing →

Don't sleep(10). Wait for the webhook. — DevHelm