Receive extraction results via HTTP POST instead of polling. Register a webhook URL and we'll send results as soon as processing completes.
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.
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:
{
"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"
}| Field | Description |
|---|---|
| event | Event type identifier |
| task_id | UUID of the completed task |
| status | complete or failed |
| result | Extracted data (null if failed) |
| project_id | UUID of the parent project |
| document_type | Document type name |
| timestamp | ISO 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}), 200import 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:
| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 4 seconds |
| 3rd retry | 16 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
| Event | Status | Description |
|---|---|---|
| task.completed | Available | Fired when document processing finishes (success or failure) |
| task.failed | Planned | Separate event for failed extractions |
| project.updated | Planned | Fired 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.