Webhooks overview
A webhook is an event-driven method of communication between applications.
Unlike typical APIs where you would need to poll for data very frequently to get it "real-time", webhooks only send data when there is an event to trigger the webhook. This makes webhooks seem "real-time", but it's important to note that they are asynchronous.
For example, if you are onboarding a new user, you can't rely on the webhook delivery as part of that flow. Typically the delivery will happen quickly, but it's not guaranteed to be delivered immediately or at all. Webhooks are best used for things like sending a notification or updating a database, but not for synchronous flows where you need to know the webhook was delivered before moving on to the next step. If you need a synchronous flow, see the onboarding guide for an example.
Clerk webhooks
Clerk webhooks allow you to receive event notifications from Clerk, such as when a user is created or updated. When an event occurs, Clerk will send an HTTP POST request to your webhook endpoint configured for the event type. The payload carries a JSON object. You can then use the information from the request's JSON payload to trigger actions in your app, such as sending a notification or updating a database.
Clerk uses Svix to send our webhooks.
You can find the Webhook signing secret when you select the endpoint you created on the Webhooks page in the Clerk Dashboard.
Supported webhook events
To find a list of all the events Clerk supports:
- In the Clerk Dashboard, navigate to the Webhooks page.
- Select the Event Catalog tab.
There is also a dedicated guide that describes the events Clerk supports for Billing.
Payload structure
The payload of a webhook is a JSON object that contains the following properties:
data: contains the actual payload sent by Clerk. The payload can be a different object depending on theeventtype. For example, foruser.*events, the payload will always be the User object.object: always set toevent.type: the type of event that triggered the webhook.timestamp: timestamp in milliseconds of when the event occurred.instance_id: the identifier of your Clerk instance.
The following example shows the payload of a user.created event:
{
"data": {
"birthday": "",
"created_at": 1654012591514,
"email_addresses": [
{
"email_address": "example@example.org",
"id": "idn_29w83yL7CwVlJXylYLxcslromF1",
"linked_to": [],
"object": "email_address",
"verification": {
"status": "verified",
"strategy": "ticket"
}
}
],
"external_accounts": [],
"external_id": "567772",
"first_name": "Example",
"gender": "",
"id": "user_29w83sxmDNGwOuEthce5gg56FcC",
"image_url": "https://img.clerk.com/xxxxxx",
"last_name": "Example",
"last_sign_in_at": 1654012591514,
"object": "user",
"password_enabled": true,
"phone_numbers": [],
"primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1",
"primary_phone_number_id": null,
"primary_web3_wallet_id": null,
"private_metadata": {},
"profile_image_url": "https://www.gravatar.com/avatar?d=mp",
"public_metadata": {},
"two_factor_enabled": false,
"unsafe_metadata": {},
"updated_at": 1654012591835,
"username": null,
"web3_wallets": []
},
"instance_id": "ins_123",
"object": "event",
"timestamp": 1654012591835,
"type": "user.created"
}The payload should always be treated as unsafe until you validate the incoming webhook. Webhooks will originate from another server and be sent to your application as a POST request. A bad actor would fake a webhook event to try and gain access to your application or data.
Delivery guarantees
At-least-once delivery
Clerk retries each event until your endpoint returns a 2xx response or the retries run out. Clerk removes duplicate events before sending them, but retries mean an event can still reach your endpoint more than once. For example, if your endpoint processes an event but Clerk never gets the response, it retries the event.
No ordering guarantee
Events aren't guaranteed to arrive in the order they occurred. For example, a user.updated event can arrive before the user.created event for the same user, or two user.updated events can arrive in reverse order. Retries make this more likely, since a failed event is resent after events that were delivered successfully in the meantime.
Handle out-of-order and duplicate events
Your webhook handler should produce the same result no matter the order events arrive in, or how many times each one arrives. The following approaches can help:
- Skip events you've already processed: Every delivery attempt for the same event carries the same
svix-idheader. If your handler has side effects, such as sending a welcome email onuser.created, store thesvix-idof each event you process and skip any you've already seen. - Compare timestamps before writing: Most objects in the payload include an
updated_attimestamp. Store it alongside the data in your database, and skip an incoming event if itsdata.updated_atis older than the stored value. This discards stale events that arrive late. Not every change to an object's related data updates itsupdated_at. To break ties, also store the event's top-leveltimestamp, and skip an event with an equalupdated_atif itstimestampis older. - Use the event
timestampwhen the object doesn't have one: Deleted objects, such as the payload of auser.deletedevent, don't includeupdated_at. Use the top-leveltimestampof the event instead. - Don't let late events undo deletions: If a create or update event arrives for an object you've already deleted, ignore it instead of recreating the object. A soft delete, such as a
deleted_atcolumn, lets you compare the event's timestamp against when the object was deleted. - Upsert instead of insert: Treat
*.createdand*.updatedevents the same way, by creating the record if it doesn't exist and updating it otherwise. This handles an update arriving before its create, and a create arriving twice. - Fetch the latest state when order matters: If your handler depends on the current state of an object, use the event as a signal and fetch the object from the Backend API instead of relying on the payload.
How Clerk handles delivery issues
Retry
Svix will use a set schedule and retry any webhooks that fail. To see the up-to-date schedule, see the Svix Retry Schedule.
If Svix is attempting and failing to send a webhook, and that endpoint is removed or disabled from the Webhooks page in the Clerk Dashboard, then the attempts will also be disabled.
Replay
If a webhook message or multiple webhook messages fail to send, you have the option to replay the webhook messages. This protects against your service having downtime or against a misconfigured endpoint.
To replay webhook messages:
- In the Clerk Dashboard, navigate to the Webhooks page.
- Select the affected endpoint.
- In the Message Attempts section, next to the message you want to replay, open the three-dot menu on the right, then select Replay.
- The Replay Messages menu will open. You can choose to:
- Resend the specific message you selected.
- Resend all failed messages since the first failed message in that date range.
- Resend all missing messages since the first failed message in that date range.
Sync data to your database
You can find a guide on how to use webhooks to sync your data to your database.
Protect your webhooks from abuse
To ensure that the API route receiving the webhook can only be hit by your app, there are a few protections you should put in place:
-
Verify the request signature: Svix webhook requests are signed and can be verified to ensure the request is not malicious. To verify the signature, use Clerk's
verifyWebhookhelper. To learn more, see Svix's guide on how to verify webhooks with the svix libraries or how to verify webhooks manually. -
Only accept requests coming from Svix's webhook IPs: To further prevent attackers from flooding your servers or wasting your compute, you can ensure that your webhook-receiving api routes only accept requests coming from Svix's webhook IPs, rejecting all other requests.
Test and debug your webhooks
To test webhooks during development, your local app needs to be reachable from the internet. See Debug your webhooks for guidance on tunneling tools and troubleshooting common issues.
Feedback
Last updated on