Skip to main content
All events share an envelope:
type WebhookPayload = {
  event:       string;          // event type, e.g. "render.completed"
  timestamp:   string;          // ISO 8601, UTC
  requestId:   string;          // unique delivery ID, matches X-RenderForge-Delivery
  briefId?:    string;          // present for events tied to a brief
  data:        unknown;         // event-specific payload — see below
  metadata?:   Record<string, unknown>; // any metadata you attached to the originating request
};
The source of truth for emitted events is src/webhooks/delivery.ts.

Events

render.completed

render.failed

carousel.completed

carousel.failed

brief.received

batch.completed


render.completed

Fires when an async render job (POST /facade/generate with operation: "render") finishes successfully and the rendered asset(s) are ready.

Payload

type RenderCompletedData = {
  status: "completed";
  renderCount: number;
  renders: Array<{
    id: string;                 // render ID
    format: "png" | "jpg" | "webp" | "mp4" | "gif";
    url: string;                // signed download URL, valid for 1h
    width: number;
    height: number;
    bytes: number;
    sha256: string;             // content hash of the rendered bytes
  }>;
};

Example

{
  "event": "render.completed",
  "timestamp": "2026-05-24T18:42:11.812Z",
  "requestId": "evt_2k4j8h9d",
  "briefId": "brief_01HV...",
  "data": {
    "status": "completed",
    "renderCount": 1,
    "renders": [{
      "id": "rnd_01HV...",
      "format": "png",
      "url": "https://cdn.kynva.ai/renders/rnd_01HV.../out.png?sig=...&exp=...",
      "width": 1080,
      "height": 1080,
      "bytes": 184231,
      "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    }]
  },
  "metadata": { "campaign_id": "summer-2026" }
}

render.failed

Fires when a render job exhausted its retries.

Payload

type RenderFailedData = {
  status: "failed";
  error: string;                // human message
  code?: string;                // matches an /errors/codes value (e.g. "RENDER_FAILED")
  request_id?: string;          // request_id of the underlying failure
};

Example

{
  "event": "render.failed",
  "timestamp": "2026-05-24T18:43:22.001Z",
  "requestId": "evt_2k4j8h9e",
  "briefId": "brief_01HV...",
  "data": {
    "status": "failed",
    "error": "Font 'AcmeSans' not found in your BrandKit.",
    "code": "RENDER_FAILED",
    "request_id": "req_2k4j8h9f"
  }
}

Fires when a multi-slide carousel batch finishes. All slides are guaranteed to be present when this fires.

Payload

type CarouselCompletedData = {
  status: "completed";
  carouselId: string;
  slideCount: number;
  zipUrl: string | null;        // if you opted into zip packaging
  slides: Array<{
    index: number;              // 0-based
    renderId: string;
    url: string;
    width: number;
    height: number;
  }>;
};

Example

{
  "event": "carousel.completed",
  "timestamp": "2026-05-24T18:50:11.000Z",
  "requestId": "evt_2k4j8h9g",
  "data": {
    "status": "completed",
    "carouselId": "car_01HV...",
    "slideCount": 5,
    "zipUrl": "https://cdn.kynva.ai/carousels/car_01HV.../slides.zip?sig=...",
    "slides": [
      { "index": 0, "renderId": "rnd_a", "url": "https://...", "width": 1080, "height": 1350 },
      { "index": 1, "renderId": "rnd_b", "url": "https://...", "width": 1080, "height": 1350 }
    ]
  }
}

Fires when a carousel batch exhausted retries — possibly with some slides successful.

Payload

type CarouselFailedData = {
  status: "failed";
  carouselId: string;
  error: string;
  failedSlideIndexes: number[]; // which slides specifically failed
  partialSlides?: Array<{       // slides that succeeded before the batch failed
    index: number;
    renderId: string;
    url: string;
  }>;
};

brief.received

Fires when an inbound brief was received via an async ingest channel (e.g., your client uploaded via signed URL). Use this to know when to start a compile.

Payload

type BriefReceivedData = {
  briefId: string;
  source: "api" | "upload" | "email" | "slack";
  receivedAt: string;           // ISO 8601
};

batch.completed

Fires when a render-job batch (multiple briefs in one job) finishes, with an aggregate summary.

Payload

type BatchCompletedData = {
  status: "completed";
  batchId: string;
  totalCount: number;
  successCount: number;
  failureCount: number;
  // Per-brief results — fetch full render details from the render-jobs API.
  results: Array<{
    briefId: string;
    status: "completed" | "failed";
    renderId?: string;
    error?: string;
  }>;
};

Subscribe to specific events

curl -X POST https://api.kynva.ai/api/v1/facade/webhooks \
  -H "Authorization: Bearer $KYNVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://your-app.com/webhooks/kynva",
    "events": [
      "render.completed",
      "render.failed",
      "carousel.completed"
    ]
  }'
Pass ["*"] to subscribe to everything (current and future). Be deliberate — your handler will receive new event types as we ship them.