API

Webhooks

Receive signed completion events on your backend and use polling only for fallback or recovery.

Lifecycle

Webhooks are completion notifications

After source ingest and generation create, webhooks usually replace polling, but `GET /generations/{job_id}` remains the recovery path.

MechanismWhen to use it
WebhookDefault for production after the generation job has been created. Best when your backend can expose a public callback endpoint and you want to avoid polling loops.
PollingUseful for local development, simple integrations, or as a fallback when webhook delivery fails.
GET after webhookUse this to recover state, fetch fresh signed URLs, or confirm final status after consumer downtime.

Mental model

A webhook tells your backend that a job reached a terminal state. It is not the authoritative record for the lifetime of the job, and it does not remove the need for a read endpoint.

Typical production flow

Ingest the source, create the job with a webhook URL, persist the returned job_id, verify the signed callback when it arrives, and call GET /generations/{job_id} only when you need repair, replay, or fresh signed URLs.

Setup

Webhook configuration

Each organization stores one signing secret. Jobs can use the default callback URL or override the destination per request.

Callback destination

Set a default callback URL in the dashboard if most jobs should go to the same consumer. Create and regenerate requests can still override the URL for one-off routing.

Signing

Every webhook delivery includes an x-katalo-signature header generated with HMAC-SHA256 over the raw request body.

Delivery

Delivery payload

Webhook payloads are small and correlation-friendly. They contain the public ids you need to map completion back to your own system.

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

Verification

Verify the signature first

Verify the signature against the raw request body before parsing or processing the payload.

import crypto from "crypto";

function verifyWebhook(body: string, signature: string, secret: string) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Implementation

Minimal consumer

A webhook consumer only needs to verify the signature, branch on `event_type`, and persist terminal state for the `job_id`.

import hashlib
import hmac
import os

from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["KATALO_WEBHOOK_SECRET"]


def verify_signature(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(
        WEBHOOK_SECRET.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


@app.post("/webhooks/katalo")
async def katalo_webhook(
    request: Request,
    x_katalo_signature: str = Header(alias="x-katalo-signature"),
):
    raw_body = await request.body()

    if not verify_signature(raw_body, x_katalo_signature):
        raise HTTPException(status_code=401, detail="invalid signature")

    event = await request.json()
    job_id = event["job_id"]
    event_type = event["event_type"]

    if event_type == "generation.completed":
        outputs = event["outputs"]
        # Persist outputs or copy them into your own storage here.
    elif event_type == "generation.failed":
        failure = event["failure"]
        # Mark the job as failed and record the failure code here.

    return {"ok": True, "job_id": job_id}

Operations

Recovery and reconciliation

Treat webhook delivery as at-least-once. Deduplicate by public ids and keep `GET /generations/{job_id}` available for repair and replay.

ConcernGuidance
DeduplicationUse `job_id` plus `event_type` as the primary dedupe key for webhook processing.
Internal correlationUse `reference` and `metadata` to reconnect the event to your internal records.
Consumer downtimeIf the consumer is unavailable, re-read the job later with `GET /generations/{job_id}`.
Expired URLsIf a signed output URL expired before you copied it, call `GET /generations/{job_id}` again and use the fresh payload.

Delivery rules

Delivery safety and retries

Webhook delivery targets only public networks and retries transient failures with bounded backoff.

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 you rotate it.
RetriesTransient delivery failures are retried with bounded exponential backoff.
AllowlistIf the organization defines a webhook hostname allowlist, delivery is restricted to that set.
Postavke kolačića

Odaberite kako Katalo koristi kolačiće

Koristimo nužne kolačiće za rad web stranice i aplikacije. Analitički kolačići pomažu nam razumjeti korištenje i poboljšati Katalo. Ako želite ponovno vidjeti ovaj banner kasnije, izbrišite kolačiće preglednika i ponovno posjetite stranicu.