Convex
Start a generation from a Convex action and receive the signed webhook on a Convex HTTP route with mynthWebhookAction.
Generate from a Convex action with the normal SDK, and receive the result
on an HTTP route with mynthWebhookAction from @mynthio/sdk/convex. The API
key stays in the Convex environment.
npm install @mynthio/sdk@mynthio/sdk/convex imports convex/server, which every Convex project
already has. Set two environment variables on the Convex deployment:
| Variable | Value |
|---|---|
MYNTH_API_KEY | A mak_ key |
MYNTH_WEBHOOK_SECRET | The whsec_ secret printed when you register the endpoint |
Queries and mutations cannot make network calls, so use an action. Create the
task with generateAsync() so the action returns right away, and collect the
file in the webhook.
// convex/generate.ts
import { v } from "convex/values";
import Mynth from "@mynthio/sdk";
import { action } from "./_generated/server";
export const generate = action({
args: { prompt: v.string() },
handler: async (_ctx, args) => {
const mynth = new Mynth(); // reads MYNTH_API_KEY
const pending = await mynth.image.generateAsync({
model: "black-forest-labs/flux.2-pro",
prompt: args.prompt,
metadata: { source: "convex" },
generate_public_access_token: true,
});
return { taskId: pending.id, publicAccessToken: pending.access.publicAccessToken };
},
});Return the task id to the client if the UI needs it. publicAccessToken lets
the browser poll that one task. It expires after an hour. Never return the
API key.
Register your deployment's HTTP actions URL plus the route path, for example
https://<deployment>.convex.site/mynth-webhook, as a webhook endpoint.
// convex/http.ts
import { httpRouter } from "convex/server";
import { mynthWebhookAction } from "@mynthio/sdk/convex";
import { internal } from "./_generated/api";
const http = httpRouter();
export const mynthWebhook = mynthWebhookAction({
imageTaskCompleted: async (result, { context }) => {
await context.runMutation(internal.images.save, {
taskId: result.taskId,
images: result.images,
});
},
imageTaskFailed: async (result) => {
console.error(result.taskId, result.errors);
},
});
http.route({ path: "/mynth-webhook", method: "POST", handler: mynthWebhook });
export default http;internal.images.save is a mutation you write. Each handler gets a mapped result or task failure
and { context, request, event }. context is the Convex action
context, so context.runQuery and context.runMutation are how it touches
your data. event.id is the webhook-id, the same on every retry, so store
it to skip events you have already handled.
The handler names are the same as in every SDK helper. See the
table on webhooks.
mynthWebhookAction returns a Convex HTTP action, so pass it straight to
http.route without wrapping it in httpAction. It runs the same checks as
the Next.js and TanStack Start helpers:
MYNTH_WEBHOOK_SECRET when a request arrives, so deploying works
before the variable is set. Pass { webhookSecret } as the second argument
to use another source.webhook-id, a webhook-timestamp
more than five minutes off, or a body whose id is not the webhook-id
answers 400.200. An error thrown by a handler
fails the request, and Mynth retries the delivery.webhook.custom URLs are rejected,
unless you pass { unsigned: { verify } } to check a token you put in the
URL. See per-request webhooks.