Integrating Clerk Webhooks with MongoDB Realm: Syncing User Data to Your Backend
In this guide, we'll walk through how to integrate Clerk webhooks with MongoDB Realm. We'll be using MongoDB Realm to create an HTTPS endpoint that will receive Clerk webhook events and sync user data to a MongoDB Atlas database.
Prerequisites:
Before you begin, you'll need to have the following:
Overview:
We'll be following these steps:
Step-by-Step Guide:
Step 1: Create a MongoDB Realm App
Step 2: Create a Custom HTTPS Endpoint
Step 3: Enable webhooks in Clerk
Step 4: Create the HTTPS Endpoint Function
const { Webhook } = require("svix");
const WEBHOOK_SECRET = "WEBHOOK_SIGNING_SECRET";
const DB_NAME = "MY_DB";
const USERS_COLLECTION_NAME = "users";
async function extractAndVerifyHeaders(request, response) {
const headers = request.headers;
const payload = request.body.text();
let svix_id, svix_timestamp, svix_signature;
try {
svix_id = headers["Svix-Id"][0];
svix_timestamp = headers["Svix-Timestamp"][0];
svix_signature = headers["Svix-Signature"][0];
if (!svix_id || !svix_timestamp || !svix_signature) {
throw new Error();
}
} catch (err) {
response.setStatusCode(400);
return response.setBody(
JSON.stringify({
success: false,
message: "Error occured -- no svix headers",
})
);
}
const wh = new Webhook(WEBHOOK_SECRET);
let evt;
try {
evt = wh.verify(payload, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
});
} catch (err) {
console.log("Webhook failed to verify. Error:", err.message);
response.setStatusCode(400);
return response.setBody(
JSON.stringify({
success: false,
message: err.message,
})
);
}
return evt;
}
function getUserDataFromEvent(evt) {
return {
clerkUserId: evt.data.id,
firstName: evt.data.first_name,
lastName: evt.data.last_name,
email: evt.data.email_addresses[0].email_address,
image: evt.data.profile_image_url,
};
}
async function handleUserCreated(evt) {
const mongodb = context.services.get("mongodb-atlas");
const usersCollection = mongodb.db(DB_NAME).collection(USERS_COLLECTION_NAME);
const newUser = getUserDataFromEvent(evt);
try {
const user = await usersCollection.insertOne(newUser);
console.log(`Successfully inserted user with _id: ${user.insertedId}`);
} catch (err) {
console.error(`Failed to insert user: ${err}`);
}
}
async function handleUserUpdated(evt) {
const mongodb = context.services.get("mongodb-atlas");
const usersCollection = mongodb.db(DB_NAME).collection(USERS_COLLECTION_NAME);
const updatedUser = getUserDataFromEvent(evt);
try {
await usersCollection.updateOne(
{ clerkUserId: evt.data.id },
{ $set: updatedUser }
);
console.log("Successfully updated user!");
} catch (err) {
console.error(`Failed to update user: ${err}`);
}
}
exports = async function syncClerkData(request, response) {
const evt = await extractAndVerifyHeaders(request, response);
switch (evt.type) {
case "user.created":
await handleUserCreated(evt);
response.setStatusCode(201);
break;
case "user.updated":
await handleUserUpdated(evt);
response.setStatusCode(200);
break;
default:
console.log(`Unhandled event type: ${evt.type}`);
response.setStatusCode(400);
}
return response.setBody(
JSON.stringify({
success: true,
message: "Webhook received",
})
);
};
Step 5: Configure Access Permissions
Step 6: Save and Deploy Your App
Step 7: Testing
Conclusion:
By following these steps, you should now have a working Clerk webhook that syncs user data to MongoDB Atlas. Consider exploring additional features and optimizations offered by Clerk and MongoDB Realm. Stay curious, and adapt the code to your specific needs, feel free to share your experiences, insights, or questions in the comments below. Happy coding!
Software Engineer
5 个月Hi sir, I think the only issue I faced for this is this is a one way sync. If I send a put request to modify the user data in mongodb it will not affect the data in clerk. How do you ensure single source of truth?