Test Clerk Webhooks Locally (Receive Clerk Webhooks on localhost)
Test Clerk webhooks locally and receive them on localhost without deploying. Inspect the real payload, forward to your handler, and verify the Svix signature.
You are building a Clerk integration — provisioning a user record when someone signs up, syncing profile changes, cleaning up when an account is deleted — and you need to see your handler react to a real user.created or session.created event. The problem is immediate: Clerk will only POST to a public URL, and your handler is running on localhost:3000. Clerk has no way to reach it.
The usual workarounds are painful. Deploying to a staging server for every code change is slow. Copying a sample payload from the docs into curl gives you a guess at the real request, not the real headers and body Clerk actually sends — and you cannot test signature verification at all without the real Svix headers. What you want is to test Clerk webhooks locally — real events, hitting your local handler, with a URL that does not change every time you restart.
This guide shows how to do exactly that.
Why receiving Clerk webhooks locally is tricky
A webhook is just an HTTP request that Clerk sends to a URL when something happens — a user is created, updated, or deleted; a session starts; an organization changes. Clerk sits on the public internet; your dev machine usually does not. It is behind a router, a corporate firewall, or both, with no public IP and no inbound ports open.
So you need something in the middle: a public endpoint Clerk can hit, that relays each request down to your laptop without you opening a single firewall port. That is what Webhook Relay does — and unlike a random tunnel URL, the endpoint is stable, so you configure Clerk once and never touch it again.
Step 1: Inspect the real payload with Webhook Bin
Before you write any handler code, find out what Clerk actually sends. Open the free Webhook Bin — no signup — and you get an instant public URL.
- Copy the Webhook Bin URL.
- In the Clerk Dashboard, go to Webhooks → Add Endpoint.
- Paste the URL into Endpoint URL, subscribe to the events you care about (for example
user.created,user.updated,session.created), and create the endpoint. - Use the dashboard's Send example / Testing tab to fire a test event, or trigger a real one by signing up a user.
The request lands in Webhook Bin right away. Inspect the captured request: the full JSON body (for user.* events the payload is the User object), and every header, including the Svix trio — svix-id, svix-timestamp, and svix-signature.
Now you know the exact shape of the data before writing a line of code. For more on this approach, see How to test webhooks and What is a webhook.
Step 2: Forward the events to localhost with the relay agent
Once you know the payload, route those same events into your local handler. Sign up for Webhook Relay, install the relay agent (CLI or Docker), and create a bucket — say clerk. The bucket gives you a stable public input endpoint.
Start forwarding to your local server:
relay forward --bucket clerk http://localhost:3000/api/webhooks/clerk
The agent opens an outbound connection to Webhook Relay and streams every incoming request down to your local route. Because the connection is outbound, there are no firewall ports to open and no public IP needed — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. Running in Docker? The same command works in the official webhookrelay/webhookrelayd image. Full details are in the localhost forwarding docs.
Now set the Clerk endpoint's URL to your Webhook Relay endpoint (or just create it there from the start). Sign up a test user and watch it arrive on localhost.
Clerk-specific configuration and quirks
A few Clerk details worth knowing:
- Where to add it: Clerk Dashboard → Webhooks → Add Endpoint. Each endpoint has its own URL and its own signing secret.
- Subscribe to events explicitly: Clerk does not send everything by default. Choose the events you want —
user.created,user.updated,user.deleted,session.created,organization.created, and so on. Branch on thetypefield in the body. - The payload wraps the object: the body has a
type(the event name), adataobject (the resource — foruser.*events that is the full User object), and anobjectfield. Readdatafor the entity,typeto decide what to do. - Signing secret: each endpoint shows a Signing Secret (it starts with
whsec_). You will need it for verification — store it asCLERK_WEBHOOK_SIGNING_SECRET. - It is Svix under the hood: Clerk uses Svix to deliver webhooks, which is why you see
svix-*headers rather than a single Clerk-specific signature header. Svix also handles retries, so build your handler to be idempotent (thesvix-idis a stable delivery ID you can dedupe on).
Step 3: Verify the Clerk webhook signature
Clerk's webhooks are signed by Svix. Each request carries svix-id, svix-timestamp, and svix-signature. The signature is a base64-encoded HMAC-SHA256 computed over the concatenation of the svix-id, the svix-timestamp, and the raw request body, using your endpoint's signing secret. The svix-signature header can contain multiple space-separated signatures, so compare against each.
The strongly recommended approach is to not hand-roll this. Use the svix library's Webhook(secret).verify(payload, headers), or Clerk's framework helper verifyWebhook(req) (which reads CLERK_WEBHOOK_SIGNING_SECRET automatically and throws on a bad signature). These check the timestamp window and the HMAC for you. Skipping verification — even for a notification-only handler — leaves the endpoint open to spoofed events.
To understand what is happening under the hood, paste a captured body, your signing secret, and the received signature into the free HMAC signature verifier. For language-specific code and the common pitfalls (reading the body after a JSON parser has consumed it, the signed payload being id.timestamp.body, timing-safe comparison), read Verify a webhook signature.
Replay and iterate
This is where local development gets fast:
- Replay from Clerk — the Webhooks dashboard keeps a delivery log and lets you resend a past message, which re-runs it against your handler.
- Replay from Webhook Relay — past requests are stored on your bucket, so you can resend a captured event without touching Clerk at all. The
svix-*headers are preserved, so signature verification still works on replay. - Iterate on your handler by editing code and replaying the same delivery until it behaves correctly. No commits, no pushes, no deploys just to test a code path.
Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or come back next week — the Clerk configuration never needs to change.
Get started
- Inspect the real payload in the free Webhook Bin — no signup needed.
- Create a Webhook Relay account, install the agent, and run
relay forward --bucket clerk http://localhost:3000/api/webhooks/clerk. - Point your Clerk webhook endpoint at the stable URL, sign up a test user, and watch it hit
localhost.
You will be testing real Clerk events against your local handler in a few minutes — no deploys, no open firewall ports, and a URL you configure exactly once.
