# 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

```bash
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.

```ts
// 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.

```ts
// 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](https://mynth.io/docs/concepts/webhooks.md#receive-events).

## 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](https://mynth.io/docs/concepts/webhooks.md#receive-events):

- It reads `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.
- A missing or bad signature, a timestamp more than five minutes off, a
  missing `X-Mynth-Delivery`, or an `X-Mynth-Event` header that does not match
  the body answers `400`.
- 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.custom` URLs are rejected.
  Register the endpoint instead.

## Next steps

- [Webhooks](https://mynth.io/docs/concepts/webhooks.md): registering the endpoint, events, sources, and retries.
- [Tasks](https://mynth.io/docs/concepts/tasks.md): statuses and how long the file lasts.
