Architecture
Before you start
You need:- a Convex project with auth configured (
ctx.auth.getUserIdentity()) - a Mynth API key
- a dashboard webhook (created in a later step)
- actions for third-party API calls (
generateAsync) - HTTP actions for incoming webhooks
- queries for reactive UI
1. Environment variables
Set these on your Convex deployment:| Variable | Purpose |
|---|---|
MYNTH_API_KEY | Server-side Mynth API key |
MYNTH_WEBHOOK_SECRET | Signature secret from the Mynth dashboard |
2. Schema
Oneimages table is enough for a first integration:
3. Database helpers
Put queries and mutations inconvex/images.ts:
listByMynthTaskId is public and scoped to the signed-in user. getByMynthTaskId is internal for the webhook handler.
4. Start generation from a Convex action
Createconvex/imagesActions.ts. Pattern:
- Call
mynth.image.generateAsync()(returns aTaskAsyncimmediately). - Create one pending row per expected image, keyed by
task.id. - Return
mynthTaskIdso the UI can subscribe.
generateAsync succeeds so a failed API call does not leave orphan rows.
With a dashboard-managed webhook (next steps), you do not pass webhook on each request.
5. Handle signed webhooks in convex/http.ts
@mynthio/sdk/convex provides mynthWebhookAction(), which:
- verifies
X-Mynth-Signature(HMAC-SHA256) usingMYNTH_WEBHOOK_SECRET - requires
X-Mynth-Event - routes on
payload.eventto your handlers - passes Convex
contextso you can run queries and mutations
| Handler | Event |
|---|---|
imageTaskCompleted | task.image.generate.completed |
imageTaskFailed | task.image.generate.failed |
imageTaskCompleted can still include per-image failures (image.status === "failed" with error.code). imageTaskFailed means the whole task failed — there is no result payload.
Pass { webhookSecret: "..." } as the second argument to mynthWebhookAction if you prefer not to use the env var.
6. Create the webhook in the Mynth dashboard
Use the dashboard webhooks page:With a dashboard-managed webhook, do not attach a
webhook object on each
generateAsync(...) call for this flow.7. Render results in React
Convex queries are reactive. After the action returns a task ID, subscribe withuseQuery; the UI updates when the webhook patches rows.
- Action starts async work and returns
mynthTaskId. - UI subscribes by that ID.
- Webhook updates rows (success or failure per image, or whole-task failure).
- Convex pushes the latest state into the UI.
Map webhook results to rows
Option 1: Match by mynthTaskId and array order
Used above. Create pending rows when the task starts, look them up with getByMynthTaskId, and match payload.result.images[index] to rows[index].
Best for prototypes and one task with a known image count.
Option 2: Put Convex row IDs in metadata
Create rows first, send their IDs in metadata, attach mynthTaskId after the API call, then update by ID in the webhook from payload.request.metadata:
payload.request.metadata.imageRowIds and patch each ID directly instead of looking up by task and index.
Use this when you need deterministic row-level mapping. If generateAsync fails after rows were created, clean up or mark those rows failed yourself.
Metadata is returned on the task and in webhook payloads. Max size: 2 KB — see Use Metadata.
Request-level custom webhooks
Dashboard-managed webhooks are the default for this Convex pattern. Use request-level custom webhooks only when you need a different destination per request, a tenant-specific URL, or a temporary endpoint:Next steps
Convex integration
mynthWebhookAction handlers and event map.Use Webhooks
Delivery model, signatures, and event payloads.
Batch Generation
Multi-model and multi-prompt generation patterns.
Tasks and Polling
Compare webhooks with SDK task polling.