Integrating Clerk Webhooks with MongoDB Realm: Syncing User Data to Your Backend

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:

  • Create a MongoDB Realm app.
  • Create a custom HTTPS endpoint.
  • Enable webhooks in Clerk.
  • Create the HTTPS endpoint function.
  • Configure access permissions.
  • Save and deploy your app.
  • Testing.

Step-by-Step Guide:

Step 1: Create a MongoDB Realm App

  • Open your MongoDB project and navigate to the App Services tab.
  • Click the "Create a New App" button and follow the recommended instructions.

Step 2: Create a Custom HTTPS Endpoint

  • In the App Services UI of your Realm app, select "HTTPS Endpoints" from the left navigation menu.
  • Click "Add An Endpoint."
  • Define the endpoint Route (e.g., "/webhook").
  • Copy the callback URL to your clipboard.
  • Choose the HTTP method "POST" and a response type "JSON".
  • Move to the function section, click "Select a function" and then click "+ New Function."
  • Enter the name for the new function (e.g., syncClerkData) and scroll down to Save Draft.

Step 3: Enable webhooks in Clerk

  • To enable webhooks, go to the Clerk Dashboard and navigate to the Webhooks page. Select the "Add Endpoint" button.
  • Complete the form specifying the URL of your backend endpoint (Paste the link you copied earlier).
  • Specify the events you want to receive (select "user.created" & "user.updated").
  • Once you click the Create button, you'll be presented with your webhook endpoint dashboard.
  • Retrieve the webhook signing secret and save it for later.

https://clerk.com/docs/users/sync-data

Step 4: Create the HTTPS Endpoint Function

  • Go back to the App Services UI of your Realm app, and click "Functions" in the left navigation menu.
  • Choose the function you added earlier.
  • Remove the default code and replace it with the provided code below in the Function Editor. Ensure you replace the WEBHOOK_SECRET, DB_NAME, and USERS_COLLECTION_NAME with the correct values.
  • Note: Add the svix package to the function dependencies by clicking the "Add Dependency" button.

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

  • To allow Clerk webhooks to call the HTTPS endpoint, update the Authentication settings.
  • Click on the "Settings" tab and choose "System" in the Authentication section.

Step 6: Save and Deploy Your App

  • Click "Save Draft."
  • Once the draft is saved, click on "REVIEW DRAFT & DEPLOY" to deploy your function.

Step 7: Testing

  • Trigger events in Clerk (user creation or update).
  • Check the MongoDB Atlas database to ensure that user data is synced.

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!

Ng Chin Chia

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?

回复

要查看或添加评论,请登录

Elmehdi lahmidi的更多文章