Webhooks
Receive signed HTTPS notifications when a budget is crossed, an invoice is issued, a request is refused by policy or a key is revoked.
Webhooks tell your systems when something happens in your organization. Every delivery is signed, retried on failure and recorded in a delivery log.
Open Admin → Webhooks. You need the Edit the organization’s profile permission (admin:org).
Adding an endpoint
Choose Add endpoint and enter:
- an HTTPS URL. On the hosted service it must be a public address on port 443; a self-hosted installation can deliver to any port;
- a description;
- the events to send.
The endpoint’s signing secret is shown once. Store it with your receiver. An organization can have up to 20 endpoints.
Events
| Event | When |
|---|---|
budget.threshold_crossed | A budget first passes 50, 80 or 90% of its limit in a period |
budget.exhausted | A budget reaches 100% of its limit; requests against it are refused |
billing.invoice_created | A monthly invoice is issued |
billing.invoice_paid | An invoice is paid |
billing.payment_failed | A payment attempt fails |
billing.state_changed | Savings switch between active and pass-through |
governance.guardrail_blocked | Prompt injection screening blocks a prompt |
governance.dlp_blocked | Data loss prevention blocks or masks a request |
governance.model_access_denied | A caller asks for a model it may not use |
governance.rate_limit_exceeded | A caller exceeds a rate limit |
security.api_key_created | A key is created or rotated in |
security.api_key_revoked | A key is revoked, deleted, or removed with its member |
security.member_invited | Someone is invited (hosted) or added (self-hosted) |
security.member_removed | A member is removed |
security.sso_connection_changed | A single sign-on connection is created, changed or deleted |
webhook.ping | Send test on an endpoint |
The four governance.* events are sent at most once a minute for each caller and rule; every refusal is still on the Activity page. No event contains prompt text, a detected value or a secret.
What your receiver gets
One POST per event and endpoint:
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: acog-webhooks/1
Acog-Event: budget.threshold_crossed
Acog-Delivery: whd_8Kd2…
Acog-Signature: t=1792161166,v1=5f0c3a…
{"id": "whev_3xQ…", "type": "budget.threshold_crossed", "created_at": "2026-10-16T14:32:18Z",
"data": {"scope": "org", "pool_id": "…", "window": "month", "pct": 80, "used_usd": 4000, "limit_usd": 5000}}
id identifies the event and is the same on every attempt and every endpoint. Acog-Delivery identifies this event’s delivery to this endpoint and is the same on every retry: use either to ignore duplicates.
Verifying the signature
Acog-Signature carries a timestamp t and v1, the hex HMAC-SHA256 of "<t>.<raw body>" keyed with the endpoint’s secret.
import hashlib, hmac, time
def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
timestamp, signature = parts["t"], parts["v1"]
if abs(time.time() - int(timestamp)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Verify against the raw body exactly as received, compare in constant time, and reject a timestamp more than five minutes from now.
Responding and retries
Answer with any 2xx status within 10 seconds, then do the work. Anything else is a failure, including a redirect (redirects are not followed).
- A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours.
- After 50 consecutive failures the endpoint is switched off; the console says why.
- A delivery still undelivered after 3 days is given up.
- Delivery is at least once and events are not ordered.
Events that happen while an endpoint is switched off are not queued for it.
Managing an endpoint
Open an endpoint to see its delivery log (every attempt and its payload, kept 30 days), change its events, switch it off or on, rotate its secret (the old secret stops working immediately) or send a test.