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.
Setup#
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 wbs_ secret printed when you register the endpoint |
Generate from an action#
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" },
});
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.
Receive the webhook#
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 (payload, { context }) => {
await context.runMutation(internal.images.save, {
taskId: payload.task.id,
images: payload.result.images,
});
},
imageTaskFailed: async (payload) => {
console.error(payload.task.id, payload.errors);
},
});
http.route({ path: "/mynth-webhook", method: "POST", handler: mynthWebhook });
export default http;internal.images.save is a mutation you write. Each handler gets the payload
and { context, request, deliveryId }. context is the Convex action
context, so context.runQuery and context.runMutation are how it touches
your data. deliveryId is the X-Mynth-Delivery value, the same on every
retry, so store it to skip deliveries you have already handled.
The handler names are the same as in every SDK helper. See the
table on webhooks.
Behavior#
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:
- It reads
MYNTH_WEBHOOK_SECRETwhen a request arrives, so deploying works before the variable is set. Pass{ webhookSecret }as the second argument to use another source. - A missing or bad signature, a timestamp more than five minutes off, a
missing
X-Mynth-Delivery, or anX-Mynth-Eventheader that does not match the body answers400. - A signed event with no handler answers
200. An error thrown by a handler fails the request, and Mynth retries the delivery. - Unsigned deliveries from per-request
webhook.customURLs are rejected. Register the endpoint instead.