Oligon Receipts is in private beta — request access.
Core concepts
Webhooks

Webhooks

Webhooks deliver asynchronous events to your server: a long-running extraction completes, a member is invited, a billing event fires. Each delivery is signed with HMAC-SHA256 so you can verify authenticity.

Setup

  1. Portal → Webhooks → Add endpoint.
  2. Enter a public HTTPS URL.
  3. Select the event types to subscribe to (or "All").
  4. Copy the signing secret (whsec_…). Store it like an API key.

Event types

EventFires when
receipt.completedExtraction finished successfully.
receipt.failedExtraction errored out (low confidence, unsupported document, etc.).
apikey.createdAn admin minted a new API key.
apikey.revokedA key was revoked or expired.
member.invitedA new teammate was invited.
billing.usage_thresholdOrg crossed 80% / 100% of its monthly quota.

Payload

{
  "id": "evt_01HQX...",
  "type": "receipt.completed",
  "org_id": "org_01HQX...",
  "created_at": "2026-06-08T14:32:11Z",
  "data": {
    "receipt": { "id": "rcp_01HQX...", "status": "completed", "total": "47.50" }
  }
}

Verifying the signature

We send a header X-Oligon-Signature: sha256=<hex> over the raw, unaltered request body.

from fastapi import FastAPI, Request, HTTPException
from oligon_receipts import OligonReceipts
 
app = FastAPI()
SECRET = "whsec_..."
 
@app.post("/webhooks/oligon")
async def handler(request: Request):
    body = await request.body()
    sig = request.headers.get("x-oligon-signature", "")
    if not OligonReceipts.verify_webhook(body, sig, SECRET):
        raise HTTPException(400, "invalid signature")
 
    event = await request.json()
    # ... dispatch on event["type"]
    return {"ok": True}
⚠️

Read the body before any framework deserialises it. Frameworks like Express, Koa, and FastAPI may re-encode JSON (adding whitespace) which invalidates the signature.

Retries

Failed deliveries are retried with exponential backoff over 72 hours:

T+0s    T+10s   T+1m    T+5m    T+30m   T+2h    T+6h    T+12h   T+24h   T+48h   T+72h

We consider any 2xx within 10 s as a success. After 72 h the event lands in Webhook Events → Failed and stays queryable via /v1/webhooks/events for 30 days.

Idempotency

Always design your handler to be idempotent: dedupe on event.id, not on receipt.id. The same event can be delivered more than once during a network partition.