cabyambo
cabyambo3d ago

Calling Convex from an outside server

Ah that makes sense. Is it possible to use the secret but pass it as an argument to a mutation/query? Would it be secure passing it as a query/mutation argument?
15 Replies
Clever Tagline
I think so. Once the HTTP action handler verifies the request, it can use ctx.scheduler to schedule an internal mutation. Internal functions can only be called from other functions within Convex, so it's safe to pass whatever you want. If you want to call a query and have the return from that query function passed back to your other server, you'd need to use await ctx.runQuery to capture the value. Have you used Convex HTTP actions before?
cabyambo
cabyamboOP3d ago
I see, I'm trying to see if there I can call the query directly from my server, without the http action wrapper around it. So:
fetchMutation(api.someMutation, {secret: process.env.CONVEX_SECRET})
fetchMutation(api.someMutation, {secret: process.env.CONVEX_SECRET})
Clever Tagline
There's also the HTTP API, which allows you to call queries, mutations, and actions directly. https://docs.convex.dev/http-api/
Convex HTTP API | Convex Developer Hub
Connecting to Convex directly with HTTP
Clever Tagline
I haven't used that myself
cabyambo
cabyamboOP2d ago
Alright, using the convex http action doesn't seem possible for my use case because I need the node runtime, so I'm back to using nextjs route handlers to handle the incoming webhooks. Ideally, I'd be able to call fetchMutation directly from my route handler. Can I pass the shared secret as a function argument to a mutation, or is it necessary to wrap the mutation in a httpAction, and pass the shared secret as an authorization header?
Clever Tagline
As for the first part, you can add the "use node" directive to the top of the file containing the HTTP action, and then you can use whatever requires the Node runtime in that action. (more here: https://docs.convex.dev/functions/actions#choosing-the-runtime-use-node) I'm not familiar with fetchMutation (don't use Next.js), so I'm not quite sure what (if anything) can be done with it.
Can I pass the shared secret as a function argument to a mutation
Yes, but I just don't know the potential security risks of that. Would you be able to share more details about what you're trying to accomplish? Maybe that can shed some light on the most appropriate path to take.
cabyambo
cabyamboOP2d ago
IIUC only non http actions can have the use node directive? https://docs.convex.dev/functions/http-actions#limits I could create a normal action that has the use node directive, then schudule that action to run from the http action, but then the issue becomes I can't get the result of that action running. And iiuc I must schedule it because I can't import a file that has the use node directive into a file that does not have the directive, I'm trying to update my convex db when I receive webhooks (in this case the webhooks are form Polar). The polar api for validating the webhook request was throwing this erro ReferenceError: Buffer is not defined. I'm not terribly familiar with it, but from my searching it appears it is because I need to be in a node runtime. I've setup the system where I pass the secret as a function argumanet (but still concerend about the security rsisks here). Another ideal solution would be to pass an auth token to the authorization header of the http function endpoints onvex exposes for our mutations/queries, but I am using WrkOs for auth and it doesn't appear that that will work? (Not fully sure on this part)
HTTP Actions | Convex Developer Hub
Build HTTP APIs directly in Convex
Clever Tagline
Ah, I forgot about that limitation with HTTP actions. It sounds like what you're trying to do is similar to what I'll be attempting in the near(ish) future. I'm migrating our business logic from DigitalOcean to Convex, and I've got endpoints on DO that receive and validate webhooks from Stripe before pushing the data along for processing. In my head I though it would be easy to just port those to Convex as HTTP actions, but now I'm wondering if I'll hit a similar snag because of the Stripe package that I use for validation.
cabyambo
cabyamboOP2d ago
Oh man yea very similar. Hopefully the stripe package doesn't require node
Clever Tagline
I'm pretty sure it does, but that's a challenge for later me to address. For your case, can you clarify how the secret passing comes into play? If I understand you correctly, your Next.js app is receiving the webhook via an endpoint defined on the server, correct? Just trying to understand the overall flow of the process. Currently there are some gaps in my mental picture of what's going on and where Convex fits in.
cabyambo
cabyamboOP2d ago
If I understand you correctly, your Next.js app is receiving the webhook via an endpoint defined on the server, correct?
That's correct! I have a route handler that handels incoming webhooks form polar. In that route handler I need to call convex functions. Those convex functions must be exposed because they are called from the route handler. And I can't use the traditional means for auhting those convex functions because the caller (nextjs route handler) doesn't have access to client side auth to pass to convex Here's what I've done in my route handler if it helps paint a better picture
export const POST = Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onSubscriptionUpdated: handleSubscriptionUpdated,
});

// Update the customers subscription tier in our db
async function handleSubscriptionUpdated(
payload: WebhookSubscriptionUpdatedPayload
) {
const userId = payload.data.customer.externalId;
if (!userId) {
throw new Error("No external ID.");
}

const tier = getTierForProduct(payload.data.productId);

await fetchMutation(api.server.user.updateUserSubscriptionTier, {
secret: process.env.CONVEX_SHARED_SECRET!, // Is this secure?
tier: tier,
userId: userId as Id<"users">,
});
}
export const POST = Webhooks({
webhookSecret: process.env.POLAR_WEBHOOK_SECRET!,
onSubscriptionUpdated: handleSubscriptionUpdated,
});

// Update the customers subscription tier in our db
async function handleSubscriptionUpdated(
payload: WebhookSubscriptionUpdatedPayload
) {
const userId = payload.data.customer.externalId;
if (!userId) {
throw new Error("No external ID.");
}

const tier = getTierForProduct(payload.data.productId);

await fetchMutation(api.server.user.updateUserSubscriptionTier, {
secret: process.env.CONVEX_SHARED_SECRET!, // Is this secure?
tier: tier,
userId: userId as Id<"users">,
});
}
Clever Tagline
Thanks for clarifying. I think the only part I'm not 100% sure of is the security of passing the Convex secret to the mutation in the fetchMutation call. Like I said, I don't use Next.js, so those server side functions are totally foreign territory. I'm gonna tag @erquhart for some help with this. He might also have other ideas on how to pull this off.
erquhart
erquhart2d ago
You don't have to schedule, you can use ctx.runAction() to call the node action and await it.
cabyambo
cabyamboOP2d ago
Ah I see, thanks! I think with having to spin up a new action I honestly will just keep this one in nextjs. Do you know if it's secure to pass the secret as a function argument?
erquhart
erquhart2d ago
No different than passing as a header for server to server purposes, just be aware of any logging that might expose it

Did you find this page helpful?