Warning

Webhooks are currently in beta. The API surface may change. If you encounter issues, fall back to polling via /task_status.

Setup

Register a webhook endpoint for your project. You'll receive a secret for verifying request signatures.

Register a webhook
curl -X POST https://api.helvetii.ai/projects/{project_id}/webhooks \
  -H "Authorization: Bearer vnd_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhook",
    "events": ["task.completed"],
    "secret": "whsec_your_webhook_secret"
  }'
urlHTTPS endpoint that accepts POST requests. Must respond with a 2xx status within 10 seconds.
eventsArray of event types to subscribe to. Currently supported: task.completed.
secretSecret key for HMAC-SHA256 signature verification. Use a strong random string with whsec_ prefix.

Payload format

When a task completes, we send a POST request to your registered URL with the following JSON body:

task.completed event
{
  "event": "task.completed",
  "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "complete",
  "result": {
    "invoice_number": "INV-2024-0042",
    "date": "2024-11-15",
    "vendor": "Acme Corp",
    "total_amount": 1250.00,
    "currency": "CHF"
  },
  "project_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "document_type": "invoice",
  "timestamp": "2024-11-15T10:30:12Z"
}
FieldDescription
eventEvent type identifier
task_idUUID of the completed task
statuscomplete or failed
resultExtracted data (null if failed)
project_idUUID of the parent project
document_typeDocument type name
timestampISO 8601 timestamp

Signature verification

Every webhook request includes an X-Vindonissa-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret. Always verify this signature before processing the payload.

import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_webhook_secret"


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


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Vindonissa-Signature", "")
    if not verify_signature(request.data, signature):
        return jsonify({"error": "invalid signature"}), 401

    event = request.json
    if event["event"] == "task.completed":
        result = event["result"]
        # Process the extracted data
        print(f"Task {event['task_id']} completed: {result}")

    return jsonify({"received": True}), 200
import express from "express";
import crypto from "crypto";

const app = express();
const WEBHOOK_SECRET = "whsec_your_webhook_secret";

app.use(express.raw({ type: "application/json" }));

function verifySignature(payload: Buffer, signature: string): boolean {
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-vindonissa-signature"] as string;
  if (!verifySignature(req.body, signature)) {
    return res.status(401).json({ error: "invalid signature" });
  }

  const event = JSON.parse(req.body.toString());
  if (event.event === "task.completed") {
    const result = event.result;
    // Process the extracted data
    console.log(`Task ${event.task_id} completed:`, result);
  }

  res.json({ received: true });
});

app.listen(3000);

Retry policy

If your endpoint returns a non-2xx status or fails to respond within 10 seconds, we retry with exponential backoff:

AttemptDelay
1st retry1 second
2nd retry4 seconds
3rd retry16 seconds

After 3 failed attempts, the webhook delivery is marked as failed. You can retrieve the result via GET /task_status/{task_id} as a fallback.

Events

EventStatusDescription
task.completedAvailableFired when document processing finishes (success or failure)
task.failedPlannedSeparate event for failed extractions
project.updatedPlannedFired when project configuration changes

Testing

For local development, use a tunneling tool like ngrok to expose your local webhook endpoint to the internet. Register the tunnel URL as your webhook endpoint and submit a test document to trigger a delivery.