Matt Luo
Matt Luo5mo ago

How do you get the issuer for a new user account from Clerk webhooks?

I followed this documentation: https://docs.convex.dev/auth/database-auth#set-up-webhooks and was able to use a Clerk webhook in order to automatically insert a document in users Convex table. But, I'm trying to adhere to the pattern where the users table has a tokenIdentifier column. The problem is that I don't see a way to get the issuer value from the Clerk webhook.
Storing Users in the Convex Database | Convex Developer Hub
_If you're using Convex Auth the user information
13 Replies
Matt Luo
Matt LuoOP5mo ago
Observed Behavior: 1) I see in this documentation: https://docs.convex.dev/auth/database-auth#optional-users-table-schema-1 that there is a column externalId. I find that to be an unusual suggestion. It's conceptually correct that that column is an external identifier, but usually the Convex community recommends to use a tokenIdentifier column in order to make it easy to migrate to another auth solution in the future.
Anyway, I want to use a tokenIdentifier column, constructed as [issuer]|[subject]. 2) Later, the documentation https://docs.convex.dev/auth/database-auth#mutations-for-upserting-and-deleting-users has this function upsertFromClerk which is what inserts the document. Here, the externalId is getting assigned the value of subject. But I instead need the value of [issuer]|[subject] to get assigned.
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
name: `${data.first_name} ${data.last_name}`,
externalId: data.id,
};
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
name: `${data.first_name} ${data.last_name}`,
externalId: data.id,
};
Storing Users in the Convex Database | Convex Developer Hub
_If you're using Convex Auth the user information
Matt Luo
Matt LuoOP5mo ago
3) If I command_click into UserJSON, I don't see anything pertaining to the notion of an issuer. I'm referring to the issuer and subject defined in UserIdentity https://docs.convex.dev/api/interfaces/server.UserIdentity
Matt Luo
Matt LuoOP5mo ago
4) And if I go to my Clerk dashboard, go to Webhooks, go to Logs, click the relevant user.created Event Type, the "Message content" does not have any properties that contain what would be the value for issuer.
No description
Matt Luo
Matt LuoOP5mo ago
5) Since my users table uses tokenIdentifier, I replaced externalId column with tokenIdentifier in the various users.ts functions. But since externalId was only getting assigned the subject, my user document is being inserted without an issuer in the tokenIdentifier column.
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
name: `${data.first_name} ${data.last_name}`,
// todo: Need to prepend [issuer]|
tokenIdentifier: data.id,
};

const user = await userByTokenIdentifier(ctx, data.id);
if (user === null) {
await ctx.db.insert('users', userAttributes);
} else {
await ctx.db.patch(user._id, userAttributes);
}
},
});
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
name: `${data.first_name} ${data.last_name}`,
// todo: Need to prepend [issuer]|
tokenIdentifier: data.id,
};

const user = await userByTokenIdentifier(ctx, data.id);
if (user === null) {
await ctx.db.insert('users', userAttributes);
} else {
await ctx.db.patch(user._id, userAttributes);
}
},
});
No description
Matt Luo
Matt LuoOP5mo ago
Possible resolution: How would you recommend that I proceed? I still want to adhere to the tokenIdentifer column design. Should I maintain environmental variables or constants in my Next.js project that holds the issuer for each environment? Perhaps there is a way to add a ctx.auth.getUserIdentity() call to Clerk inside the HTTP action:
http.route({
path: '/clerk-users-webhook',
method: 'POST',
http.route({
path: '/clerk-users-webhook',
method: 'POST',
in order to get the tokenIdentifer. But it seems inefficient and error-prone to receive an event from a Clerk webhook, only to call Clerk again with ctx.auth.getUserIdentity(). And I don't know how the HTTP action would have the context about who the user is.
ian
ian5mo ago
Is the issuer just the domain set in the providers list in auth.config.ts? tokenIdentifier is a safeguard against multiple providers using the same ID.
Matt Luo
Matt LuoOP5mo ago
yes it is
ian
ian5mo ago
so if you have the domain set from an env variable there already, you can use that like ${process.env.CLERK_ISSUER_URL}|${data.id} right? or are you hard-coding the issuer in auth.config.ts?
Matt Luo
Matt LuoOP5mo ago
Yeah, that's fine. I will just do that I thought it would be a problem once I do preview environments, which I haven't really explored yet
ian
ian5mo ago
you can set default env vars for preview environments If you find over time that tokenIdentifier is not the best for you - maybe you prefer to use the id from clerk or track the github user id or something, you can always do a migration to another field, or have tokenIdentifier be of a different format. And if you do use http actions and set the JWT as the bearer token, you can use ctx.auth inside of those actions too, in case that wasn't clear. but I don't think clerk webhooks set that header when they call over
Matt Luo
Matt LuoOP5mo ago
Right, Clerk's user.created webhook doesn't send that issuer info. Okay, sounds good. I'll proceed with using the environment variable. I'm trying to follow Convex's recommendations very closely so I minimize the chances I need to do big migrations in the future, espeically for user data. The docs for setting up Clerk webhooks has a column externalId, so I needed to think through how to make it work with tokenIdentifier If the intention was that column hold the subject, maybe subject would have been better just to clarify that this column is intended to hold Convex API's userIdentity.subject
Matt Luo
Matt LuoOP5mo ago
Otherwise, I thought these was really good documentation: https://docs.convex.dev/auth/database-auth
Storing Users in the Convex Database | Convex Developer Hub
_If you're using Convex Auth the user information
Matt Luo
Matt LuoOP5mo ago
Clerk has a corresponding documentation for Convex, but it was harder to understand what was going on. I started with the Clerk docs. Having both is nice