Sync data to your backend with webhooks
A common set up for applications involves a frontend and some sort of backend storage system. Since authentication and user management happens on Clerk's side, data eventually needs to reach the application's backend.
The recommended way to sync data between Clerk and your application's backend is via webhooks. In this guide, you'll learn how to enable webhooks and how to set up your backend so that it is updated every time an event happens on your Clerk instance.
Given the asynchronous nature of webhooks, they might not fit in every use case out there but they are a great fit for most applications.
Enable webhooks
To enable webhooks, go to the Webhooks page in the Clerk dashboard and click on the Add Endpoint button.
You'll be presented with a form where you can specify the URL of your backend endpoint. This is the URL where Clerk will send the webhook events. You can also specify the events you want to receive. For example, if you only want to receive events related to users, you can select the user option.
Once you click the Create button, you'll be presented with your webhook endpoint dashboard. Here you can see the URL of your endpoint and the events you selected and you can also test your endpoint.
Setup your backend endpoint
Now that you have your webhook endpoint set up, you need to set up your backend endpoint to receive the events. The Clerk webhook events are sent as HTTP POST requests with a JSON body. The body of the request contains the event type and the data related to the event.
Here's an example of a webhook payload for a user.created
event:
{ "data": { "birthday": "", "created_at": 1654012591514, "email_addresses": [ { "email_address": "james@clerk.com" } ] }, "object": "event", "type": "user.created" }
To learn more about the payload structure, check out the webhooks reference.
Now you need to set up your backend endpoint to receive the webhook events. The exact steps depend on the backend framework you're using. Below are some examples for common frameworks.
If you're using Next.js with Pages Router, you can create a webhook endpoint in the pages/api
directory.
Svix provides a package for verifying the webhook signature, making it easy to verify the authenticity of the webhook events.
terminalnpm install svix
terminalyarn add svix
terminalpnpm add svix
Next you will want to retrieve the Webhook signing secret from the Clerk Dashboard. You can find this in the Webhooks section of the Clerk Dashboard. You will need to set this as an environment variable in your Next.js project.
pages/api/webhook.tsimport type { IncomingHttpHeaders } from 'http'; import type { NextApiRequest, NextApiResponse } from 'next'; import type { WebhookRequiredHeaders } from 'svix'; import type { WebhookEvent } from '@clerk/nextjs/server'; import { Webhook } from 'svix'; const webhookSecret: string = process.env.WEBHOOK_SECRET; export default async function handler( req: NextApiRequestWithSvixRequiredHeaders, res: NextApiResponse ) { const payload = JSON.stringify(req.body); const headers = req.headers; // Create a new Webhook instance with your webhook secret const wh = new Webhook(webhookSecret); let evt: WebhookEvent; try { // Verify the webhook payload and headers evt = wh.verify(payload, headers) as WebhookEvent; } catch (_) { // If the verification fails, return a 400 error return res.status(400).json({}); } const { id } = evt.data; const eventType = evt.type; if (eventType === 'user.created') { console.log(`User ${id} was ${eventType}`); res.status(201).json({}); } } type NextApiRequestWithSvixRequiredHeaders = NextApiRequest & { headers: IncomingHttpHeaders & WebhookRequiredHeaders; };
If you're using Next.js with App Router, you can create a webhook endpoint in the app/api
directory.
Svix provides a package for verifying the webhook signature, making it easy to verify the authenticity of the webhook events.
terminalnpm install svix
terminalyarn add svix
terminalpnpm add svix
Next you will want to retrieve the Webhook signing secret from the Clerk Dashboard. You can find this in the Webhooks section of the Clerk Dashboard. You will need to set this as an environment variable in your Next.js project.
app/api/webhook/route.tsimport { Webhook } from 'svix' import { headers } from 'next/headers' import { WebhookEvent } from '@clerk/nextjs/server' export async function POST(req: Request) { // You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET if (!WEBHOOK_SECRET) { throw new Error('Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local') } // Get the headers const headerPayload = headers(); const svix_id = headerPayload.get("svix-id"); const svix_timestamp = headerPayload.get("svix-timestamp"); const svix_signature = headerPayload.get("svix-signature"); // If there are no headers, error out if (!svix_id || !svix_timestamp || !svix_signature) { return new Response('Error occured -- no svix headers', { status: 400 }) } // Get the body const payload = await req.json() const body = JSON.stringify(payload); // Create a new SVIX instance with your secret. const wh = new Webhook(WEBHOOK_SECRET); let evt: WebhookEvent // Verify the payload with the headers try { evt = wh.verify(body, { "svix-id": svix_id, "svix-timestamp": svix_timestamp, "svix-signature": svix_signature, }) as WebhookEvent } catch (err) { console.error('Error verifying webhook:', err); return new Response('Error occured', { status: 400 }) } // Get the ID and type const { id } = evt.data; const eventType = evt.type; console.log(`Webhook with and ID of ${id} and type of ${eventType}`) console.log('Webhook body:', body) return new Response('', { status: 201 }) }
Svix provides a package for verifying the webhook signature, making it easy to verify the authenticity of the webhook events.
terminalnpm install svix
terminalyarn add svix
terminalpnpm add svix
Next you will want to retrieve the Webhook signing secret from the Clerk Dashboard. You can find this in the Webhooks section of the Clerk Dashboard. You will need to set this as an environment variable in your Node project.
router.post('/webhook', bodyParser.raw({type: 'application/json'}), async function(req, res) { try { const payload = req.body; const headers = req.headers; const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET_KEY) const evt = wh.verify(payloadString, svixHeaders) as WebhookEvent; const { id } = evt.data; // Handle the webhook const eventType = evt.type; if (eventType === "user.created") { console.log(`User ${id} was ${eventType}`); } res.status(200).json({ success: true, message: 'Webhook received' }) } catch (err) { res.status(400).json({ success: false, message: err.message }) } })
Testing the webhook
To test a webhook locally, you can use a tool like ngrok to expose your local server to the internet. You can then use the ngrok URL as the webhook URL in the Clerk Dashboard.
If you are testing webhooks outside of your development, you can use our webhook test requests to test your webhook. You can find these in the Webhooks section of the Clerk Dashboard.