Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.katalo.ai/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are completion notifications. After source ingest and generation create, webhooks usually replace polling, but GET /api/v1/generations/{job_id} remains the recovery path.

Webhook configuration

Each organization stores one signing secret. Jobs can use the default callback URL or override the destination per request.
SettingBehavior
Callback URLSet a default in the dashboard, or pass webhook.url on create/regenerate for one-off routing.
Signing secretOne organization secret signs every delivery until rotated.
Signature headerDeliveries include x-katalo-signature, generated with HMAC-SHA256 over the raw request body.

Delivery payload

FieldMeaning
event_typegeneration.completed or generation.failed.
job_idPublic job id to correlate parallel runs.
source_asset_idPublic source asset id for the request.
referenceYour original external identifier, echoed back if sent.
outputsSigned image URLs for passing outputs.
failureTerminal failure details if the job failed.
metadataYour original metadata echoed back in the payload.

Verify signatures

Verify the signature against the raw request body before parsing or processing the payload.
import crypto from "node:crypto";

function verifyKataloWebhook(rawBody: Buffer, signature: string, secret: string) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex"),
  );
}

Minimal consumer

export async function POST(request: Request) {
  const rawBody = Buffer.from(await request.arrayBuffer());
  const signature = request.headers.get("x-katalo-signature");

  if (!signature || !verifyKataloWebhook(rawBody, signature, process.env.KATALO_WEBHOOK_SECRET!)) {
    return new Response("Invalid signature", { status: 401 });
  }

  const event = JSON.parse(rawBody.toString("utf8"));

  if (event.event_type === "generation.completed") {
    await markJobSucceeded(event.job_id, event.outputs);
  }

  if (event.event_type === "generation.failed") {
    await markJobFailed(event.job_id, event.failure);
  }

  return Response.json({ ok: true });
}

Recovery and reconciliation

Treat webhook delivery as at-least-once.
ConcernGuidance
DeduplicationUse job_id plus event_type as the primary dedupe key.
Internal correlationUse reference and metadata to reconnect the event to your internal records.
Consumer downtimeRe-read the job later with GET /api/v1/generations/{job_id}.
Expired URLsCall GET /api/v1/generations/{job_id} again to receive fresh signed URLs.

Delivery rules

RuleBehavior
Public destinations onlyLocalhost, private IPs, link-local targets, and other non-public destinations are rejected.
Shared secretOne organization secret is reused for every webhook signature until rotation.
RetriesTransient delivery failures are retried with bounded exponential backoff.
AllowlistIf the organization defines a webhook hostname allowlist, delivery is restricted to that set.