Skip to content

Webhooks

Overview

Webhook delivery infrastructure exists in the gateway and is managed through admin-only routes under /api/v1/admin/webhooks. Those routes are intentionally hidden from the public OpenAPI schema.

Today, webhook management is provisioned operationally rather than through a public self-service API portal.

Event Types

The gateway currently recognizes these event types:

Event Meaning
assessment.completed Assessment completion event
mastery.threshold_crossed Mastery threshold crossed
key.revoked API key revoked
key.rotated API key rotated
budget.exceeded Cost budget threshold crossed

Event Envelope

{
  "id": "evt_uuid_here",
  "type": "assessment.completed",
  "api_version": "2026.03.1",
  "created_at": "2026-03-04T12:00:00+00:00",
  "data": {
    "session_id": "example",
    "score": 85.0
  }
}

Signing

Each delivery includes:

Header Meaning
X-ANFI-Signature sha256=<hex>
X-ANFI-Timestamp Unix timestamp used in the signature
X-ANFI-Event-Id Event UUID
X-ANFI-Event-Type Event type

Signature input:

{timestamp}.{raw_json_payload}

Example verification in Python:

import hashlib
import hmac
import time


def verify_webhook(payload_bytes, signing_secret, signature_header, timestamp):
    if abs(int(time.time()) - int(timestamp)) > 300:
        return False

    expected = "sha256=" + hmac.new(
        signing_secret.encode(),
        f"{timestamp}.".encode() + payload_bytes,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(signature_header, expected)

Retry and Replay

The dispatcher persists every delivery row before the first outbound HTTP call. Failure states use this backoff schedule in minutes:

1, 5, 30, 120, 480, 1440, 2880, 5760

That corresponds to:

  • 1 minute
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 8 hours
  • 24 hours
  • 48 hours
  • 96 hours

Important implementation note: the first-attempt dispatcher is live, and failed deliveries are marked with their next scheduled attempt. A general retry drainer is not yet exposed as a public workflow, so replay/retry remains an admin-operated process.

Operational Behavior

  • signing secrets are generated server-side
  • the secret is returned only once at endpoint creation
  • list operations return signing_secret: null
  • dead-letter rows can be reset to pending through the admin replay route