Skip to main content
Two delivery patterns are supported:
  • Per-request — include webhook_url on a single generation call. One-shot; fires once for that task.
  • Subscriptions — call POST /webhooks once per org; fires for every matching event thereafter.
Subscriptions are the right default for production. Per-request is handy for ad-hoc scripts or testing.

Events

Currently we fire two events:
  • run.completed — a task transitioned to status=completed
  • run.failed — a task transitioned to status=failed
Subscriptions are opt-in per event via the events field when subscribing (see below). Subscribe only to the events you actually handle.

Subscribing

POST /api/v1/webhooks
X-API-Key: $OVERTEN_API_KEY
Content-Type: application/json

{
  "url": "https://myapp.example.com/webhooks/overten",
  "events": ["run.completed", "run.failed"],
  "description": "Production production pipeline"
}
Response:
{
  "webhook_id": "whk_...",
  "url": "https://myapp.example.com/webhooks/overten",
  "events": ["run.completed", "run.failed"],
  "description": "Production pipeline",
  "active": true,
  "secret": "whsec_...",
  "created_at": "..."
}
The secret is shown only once. Save it — you need it to verify signatures. Losing it means deleting the subscription and creating a new one.

Per-request webhooks

If you just want a single callback for one task:
POST /excel/generate
{
  "prompt": "...",
  "async": true,
  "webhook_url": "https://myapp.example.com/webhooks/overten"
}
This fires against your org’s primary webhook secret (from GET /verify), not a per-subscription one. Same signature scheme.

Payload shape

POST https://myapp.example.com/webhooks/overten
Content-Type: application/json
X-Overten-Signature: sha256=<hex_hmac>
X-Overten-Timestamp: 1713312345
X-Overten-Event: run.completed
User-Agent: Overten-Webhook/1.0

{
  "event": "run.completed",
  "run_id": "run_01HXYZ",
  "task_id": "task_01HXYZ",
  "agent_name": "excel_agent_api",
  "format": "excel",
  "status": "completed",
  "download_url": "https://...",
  "edit_url": "https://...",
  "summary": "Created a 12-row sales report...",
  "occurred_at_ms": 1713312345678
}
On run.failed the shape is:
{
  "event": "run.failed",
  "run_id": "run_01HXYZ",
  "task_id": "task_01HXYZ",
  "agent_name": "excel_agent_api",
  "format": "excel",
  "error": {
    "code": "internal_error",
    "message": "..."
  },
  "occurred_at_ms": 1713312345678
}

Verifying signatures

The signature is computed as:
signed_payload = f"{timestamp}.{raw_body}"
signature      = hmac.new(secret, signed_payload, sha256).hexdigest()
Where timestamp is the X-Overten-Timestamp header value and raw_body is the exact bytes of the POST body (not re-serialized JSON).
import hmac, hashlib, time
from fastapi import Request, HTTPException

SECRET = "whsec_..."

async def verify(request: Request):
    body = await request.body()
    sig_header = request.headers.get("X-Overten-Signature", "")
    ts = request.headers.get("X-Overten-Timestamp", "")
    if not sig_header.startswith("sha256="):
        raise HTTPException(400, "missing signature")
    expected_sig = sig_header.removeprefix("sha256=")

    # Replay protection: reject > 5 min old
    if abs(time.time() - int(ts)) > 300:
        raise HTTPException(400, "timestamp too old")

    signed_payload = f"{ts}.{body.decode()}".encode()
    computed = hmac.new(SECRET.encode(), signed_payload, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(computed, expected_sig):
        raise HTTPException(400, "bad signature")

Retry policy

Non-2xx responses trigger retries with exponential backoff:
1s → 4s → 16s → 64s → 256s → 1024s → 4096s
Seven attempts total, ~1.5h total window. After the last attempt we give up; the task itself is still completed (or failed) on our side — only the notification was lost. Your application should be idempotent and not depend on the webhook firing exactly once. 4xx responses are not retried — if your endpoint returns 400/401/404, we assume your service is saying “don’t try again.”

Best practices

Return 200 immediately and enqueue the work internally. We time out at 15 seconds per attempt; if your handler runs synchronously longer than that we’ll treat it as a failure.
Retries and edge cases can cause duplicate deliveries. Key your processing on (run_id, event) and skip if already handled.
Webhook URLs are attacker-reachable. Signatures prove it came from us. Never trust the payload without verifying.
If it leaks, rotate by deleting the subscription and recreating it — the new subscription will issue a fresh secret. Old signatures will stop validating immediately.

Debugging

The dashboard’s Webhooks → Delivery log shows every attempt for the last 30 days with:
  • HTTP status received
  • Response body (truncated to 1KB)
  • Latency per attempt
  • Retry schedule remaining
If deliveries are failing, check there first before reaching out to support.