Skip to main content
See the end‑to‑end flow in the Webhooks overview.

Installation

Install the required packages:
pip install chunkr-ai fastapi uvicorn svix

Python (FastAPI)

This minimal FastAPI handler verifies the webhook signature using Svix and then calls chunkr.tasks.parse.get(task_id).
from fastapi import FastAPI, Request, Response, status
from svix.webhooks import Webhook, WebhookVerificationError
from chunkr_ai import Chunkr
import os


app = FastAPI()

CHUNKR_WEBHOOK_SECRET = (
    os.environ.get('CHUNKR_WEBHOOK_SECRET') or ''
)  # starts with "whsec_"
chunkr = Chunkr()


@app.post('/chunkr/webhooks', status_code=status.HTTP_204_NO_CONTENT)
async def webhook_handler(request: Request) -> Response:
    headers = request.headers
    payload = await request.body()  # Verify against the raw body

    try:
        svix_id = headers.get('svix-id')
        svix_timestamp = headers.get('svix-timestamp')
        svix_signature = headers.get('svix-signature')

        if not svix_id or not svix_timestamp or not svix_signature:
            return Response(status_code=status.HTTP_400_BAD_REQUEST)

        svix_headers = {
            'svix-id': svix_id,
            'svix-timestamp': svix_timestamp,
            'svix-signature': svix_signature,
        }
        msg = Webhook(CHUNKR_WEBHOOK_SECRET).verify(payload, svix_headers)  # dict
    except WebhookVerificationError:
        return Response(status_code=status.HTTP_400_BAD_REQUEST)

    task_id = msg.get('task_id')
    status_str = msg.get('status')

    # Fetch only once the task is completed
    if task_id and status_str == 'Succeeded':
        _task = chunkr.tasks.parse.get(task_id)
        if _task.output is not None:
            # Do something with _task (e.g., persist results)
            for chunk in _task.output.chunks:
                print(chunk.content)

    # No content response for webhook receivers
    return Response(status_code=status.HTTP_204_NO_CONTENT)
Run the server locally:
uvicorn main:app --reload --port 8000

TypeScript (Bun)

This minimal Bun server verifies the webhook signature using Svix and then calls chunkr.tasks.parse.get(task_id).
import Chunkr from "chunkr-ai";
import { Webhook } from "svix";

const CHUNKR_WEBHOOK_SECRET = process.env.CHUNKR_WEBHOOK_SECRET || ""; // starts with "whsec_"
const chunkr = new Chunkr();

// Webhook Endpoint
async function handleWebhook(request: Request): Promise<Response> {
  try {
    const headers = request.headers;
    const payload = await request.text();

    // Extract Svix headers
    const svixId = headers.get("svix-id");
    const svixTimestamp = headers.get("svix-timestamp");
    const svixSignature = headers.get("svix-signature");

    if (!svixId || !svixTimestamp || !svixSignature) {
      return new Response("Missing required headers", { status: 400 });
    }

    const svixHeaders = {
      "svix-id": svixId,
      "svix-timestamp": svixTimestamp,
      "svix-signature": svixSignature,
    };

    // Verify the webhook
    const wh = new Webhook(CHUNKR_WEBHOOK_SECRET);
    const msg = wh.verify(payload, svixHeaders) as any;

    const taskId = msg.task_id;
    const status = msg.status;

    // Fetch only once the task is completed
    if (taskId && status === "Succeeded") {
      const task = await chunkr.tasks.parse.get(taskId);
      if (task.output) {
        // Do something with task (e.g., persist results)
        const parseOutput = task.output;
        if (parseOutput.chunks) {
          for (const chunk of parseOutput.chunks) {
            console.log(chunk.content);
          }
        }
      }
    }

    // No content response for webhook receivers
    return new Response(null, { status: 204 });
  } catch {
    return new Response("Bad Request", { status: 400 });
  }
}

function startServer() {
  const server = Bun.serve({
    port: process.env.PORT || 8000,
    async fetch(request) {
      const url = new URL(request.url);

      if (url.pathname === "/chunkr/webhooks" && request.method === "POST") {
        return handleWebhook(request);
      }

      return new Response("Not Found", { status: 404 });
    },
  });

  return server;
}

if (import.meta.main) {
  startServer();
}
Run the server locally:
bun run main.ts