Nov 14, 2023
Dev Agrawal
This post covers how to synchronize user data from Clerk into a Convex database using Webhooks.
Composing an application out of multiple data sources can be challenging, and while Clerk’s Backend APIs work great for most use cases, Webhooks offer the next level of integration that enables you to take full advantage of your existing stack. This post will cover how to synchronize user data from Clerk into your own backend using Webhooks.
Convex
Clerk offers a first-class integration with Convex. The queries and mutations living in your Convex instance can be authenticated with a token that was created by Clerk. This integration is covered in more detail in the documentation
Convex is also a great target for synchronizing user data from Clerk using webhooks, since you can take full advantage of Convex’s realtime capabilities for user data.
If you are not sure what webhooks are and how to use webhooks with Clerk, it’s recommended to read this getting started post
To demonstrate data synchronization with Convex, we can use Convex’s Clerk starter repo
This post will:
The demo application is a chat room where users can send and receive messages. To access the room, a user needs to sign up for an account using the Clerk integration. Once signed in, the user is redirected to a chat room, which establishes a reactive query to the Convex database. This reactive query looks for the user in the database, and returns a “No Clerk User” if the user is not found, and a “Logged In” if the user is found.
Initially after signing up, the user will not be found in the database. Since the signup process happened with Clerk, Convex has no record of this user.
This is where webhooks come into play. After the user signs up with Clerk, Clerk will send an http request to a pre-defined URL with data about the sign up. This URL will point to a Convex HTTP Action, which will parse the data and create a record in the Convex database for the user. Now the client can query for this user again and receive the expected response.
But since Convex queries are reactive, the client doesn’t need to retry the query, instead it will automatically receive an update from the server with the user data. This will trigger React to re-render the UI, providing the user access to the chat room.
To look at the webhook implementation, we can open the convex/http.ts
file in this repo.
convex/http.ts1// define the webhook handler2const handleClerkWebhook = httpAction(async (ctx, request) => {3const event = await validateRequest(request);4if (!event) {5return new Response("Error occured", { status: 400 });6}7switch (event.type) { ... }8return new Response(null, { status: 200 });9});1011// define the http router12const http = httpRouter();1314// define the webhook route15http.route({16path: "/clerk-users-webhook",17method: "POST",18handler: handleClerkWebhook,19});
The handleClerkWebhook
function is responsible for… you guessed it… handling the Clerk webhook. It’s a function decorated with httpAction
and receives the HTTP request and a context object to access other Convex resources.
This function runs security validation on the request to ensure that it came from a trusted source, and runs some database mutations based on the type of event received. The mutations simply mirror the event - creating, updating, or deleting a user in Clerk simply results in creating, updating, or deleting the same user in Convex. It forms a copy of the Clerk user table within Convex.
convex/http.ts1switch (event.type) {2case "user.created":3case "user.updated": {4await ctx.runMutation(internal.users.updateOrCreateUser, {5clerkUser: event.data,6});7break;8}9case "user.deleted": {10const id = event.data.id!;11await ctx.runMutation(internal.users.deleteUser, { id });12break;13}14default: {15console.log("ignored Clerk webhook event", event.type);16}17}
This allows the most powerful feature of Convex - Queries, to access the user data directly, which means the application doesn’t need to make additional requests to Clerk to fetch the user data (also called the n+1 problem), and the user interface can get notified if the data changes (e.g. if the user changes their name or profile picture).
convex/users.ts1export const getUser = internalQuery({2args: { subject: v.string() },3async handler(ctx, args) {4return ctx.db5.query("users")6.withIndex("by_clerk_id", (q) => q.eq("clerkUser.id", args.subject))7.unique();8},9});
Webhooks are a powerful feature that enable integrations with existing backend tools, allowing you to use their technical potential to the fullest, while also using Clerk's user management capabilities to the fullest. They help glue disjointed parts of the backend together so that you can pick whatever technology suits your use case for storing and processing data.
You can explore the Clerk Webhooks docs
For more in-depth technical inquiries or to engage with our community, feel free to join our Discord. Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our Twitter account, @ClerkDev. Your journey to seamless User Management starts here!
Start completely free for up to 10,000 monthly active users and up to 100 monthly active orgs. No credit card required.
Learn more about our transparent per-user costs to estimate how much your company could save by implementing Clerk.
The latest news and updates from Clerk, sent to your inbox.