Live-Testing Webhook-Gated Flows Locally
To test a flow whose state is produced by an external webhook (Clerk, Stripe, Metronome), run the real app behind a tunnel, drive the real UI, read the server log as ground truth - and when the webhook never arrives, simulate its delivery faithfully (a signed payload to the real route) instead of mocking the unit under test.
Historical warning: the Radar-specific observations below came from the old PR #2093 path. PR #2658 removed the local non-production Radar allow bypass, login Radar bridge, and IP fallback design. Use Radar Registration Gate as the current architecture; use this page for the live-testing technique. Source: Dedalus PR #2658, 2026-06-23
Many product flows only "work" once an external provider calls back: a Clerk organization.created webhook provisions a Stripe customer, a Stripe webhook grants signup credits, a Metronome webhook reconciles billing. Unit tests mock these away, so the integration seam - the part that actually breaks in production - goes untested. The technique below exercises that seam on a local dev server.
When to use this workflow
Use this workflow when the state under test is created by an external provider callback, delayed webhook, tunnel-delivered event, or provider dashboard action. Do not use it to replace unit tests. Use it when unit tests already passed and the question is whether the real seam works under local/dev conditions.
The procedure
- Run the app behind the team tunnel so providers can reach localhost. Dedalus uses a named cloudflared tunnel:
pnpm dev website --tunnelmapsdev.dedaluslabs.ai -> localhost:3000(creds in.cloudflared/tunnels/website.json). Browse the tunnel hostname, notlocalhost, so the client gets a real IP and the session matches the webhook origin. Note: running the tunnel means your machine receives the dev instance's webhook traffic - tear it down when done (Agent Process Tracking, Audit, And Cleanup). - Drive the real flow with provider test fixtures. Clerk dev accepts
<name>+clerk_test@example.comwith verification code424242, so signup/login can be automated without real email. - Read the server log as ground truth. Browser-side request counts get inflated by reloads and HMR; the dev server's request log gives authoritative status codes and counts (
rg -o 'POST /api/<route> [0-9]+' devlog | sort | uniq -c). Some flows are silent on success and only log on failure - never rely on "seeing a success log." - When provisioning never arrives, simulate the webhook delivery. If the provider isn't delivering to the tunnel, POST a correctly-signed synthetic event to the real local webhook route. This runs the real handler (creates real test-mode entities), so it is faithful, not a stub. For Clerk this is an svix-signed body to
/api/clerk; sign with the route'sCLERK_WEBHOOK_SECRETvia thesvixWebhook.sign(id, timestamp, payload)helper and sendsvix-id/svix-timestamp/svix-signatureheaders. Read the target entity id from the failure log, sign it, POST, then re-drive the UI; the gated response flips (e.g.409 -> 202).
This is dev/test only - test-mode Stripe, dev database. Never aim a tunnel or simulated webhook at production.
Finding solutions to problems as they occur
The payoff of a live test is the chain of blockers it surfaces; each one is diagnosed from evidence (server log, network, the code path) before patching. From the Radar session, in order:
- Stale worktree (a component named in the PR was missing) - the worktree branch was the pre-rebuild version (153 ahead / 733 behind). Detach to the real PR head; do not reset the diverged branch (Git Workflow).
Missing SUPABASE_URLon build - the worktree had no.env.local. Copy the gitignored env from the main checkout.- A retry storm -
/api/radar/loginre-fired ~2/sec forever on a409(1,353 requests in one session) because the client poll only stopped on success. Fix: stop the poll after one completed attempt per context. Verified bounded (2 requests) afterward. - API won't boot (
FileNotFoundError: model_catalog.toml) - provider catalog not compiled into a fresh venv; irrelevant to the route under test, so skipped. - Provisioning never happened even with the tunnel - the dev Clerk instance wasn't delivering webhooks; solved by simulating the signed
organization.createddelivery. - Expected
200, got202- historical #2093 behavior: the evaluation short-circuited off-production (VERCEL_ENV !== "production"). PR #2658 removed that bypass; strict Radar signup tests now mock Stripe at the boundary instead of shipping an alternate local allow path.
Transferable lessons
- Simulate the dependency, not the unit. Mocking the function under test proves nothing; replaying the provider's call through the real route exercises the real seam.
- The server log is the source of truth for counts and status codes; the browser lies (reloads, HMR, StrictMode double-mount).
- "Success is silent" is common - confirm via network + server log, not console.
- Read the guard before calling a result a bug - environment-gated branches (
VERCEL_ENV, feature flags) legitimately change the expected status code by environment. - Persist the procedure where the code lives. The Dedalus-specific runbook ships in-repo at
apps/website/docs/radar-live-testing.mdso it is reviewed and stays current with the code.
Validation
A completed live test records:
- tunnel hostname and target local service
- provider/test fixture used
- server-log evidence and status transitions
- whether the provider delivered the webhook naturally or a signed synthetic event was replayed
- cleanup action for tunnel/dev server
The workflow is not complete if it only reports browser UI state. Browser state is a symptom; server logs and provider events are the proof.
Run Contract
This workflow follows Workflow Run Contract: name the sources, write output or no-op proof, promote only durable facts, update state only when the run really completed, refresh generated surfaces when durable pages or skills change, and log user-visible work.
Timeline
- 2026-07-01 | Added usage boundaries and validation proof so the page serves as a reusable webhook-gated live-testing workflow rather than only a historical Radar note. Source: User request, 2026-07-01
- 2026-06-23 | Corrected current strict Radar PR references from the planning placeholder to PR #2658 and kept this page scoped to live-testing technique. Current architecture remains Radar Registration Gate. Source: Dedalus PR #2658, 2026-06-23
- 2026-06-22 | Added historical warning after the strict Radar rewrite. The live-testing method remains useful, but Radar architecture now lives in Radar Registration Gate and no longer includes the local non-production allow bypass, login Radar bridge, or IP fallback path. Source: Dedalus strict Radar rewrite plan, 2026-06-22
- 2026-06-22 | Captured from the Dedalus PR #2093 (ENG-377, Stripe Radar registration) live-test session. Tested the radar signup + login flow locally via the cloudflared tunnel, found and fixed a no-backoff retry storm in
RadarLoginBridge(1,353POST /api/radar/login409s in one session -> bounded to 2), and reached the gated happy path by simulating a signed Clerkorganization.createdwebhook delivery to provision the org's Stripe customer (409 -> 202). In-repo runbook + PR write-up authored the same session. Source: PR #2093 live-test session, 2026-06-22