Usage callbacks

Receive a signed webhook when a team-billed run settles or is released.

Enterprise team owners can configure a signed webhook instead of polling. Under Settings → Teams → (team) → Keys → Usage callback, an owner sets an https:// callback URL, enables it, and receives a signing secret once (it can be rotated, or a test event sent, from the same panel). Key minting on that tab is covered on Teams and team wallets.

Settings → Teams keys tab with a usage callback URL enabled
Keys: usage callback URL and enable toggle

The callback host must resolve to a public IP address. Deliveries to private or loopback addresses are not attempted.

Whenever an AI run billed to that team (billed_team_id on the charge) reaches a terminal billing state, Coda POSTs a JSON event to the URL:

  • usage.settled: the charge settled, including debt (partial payment).
  • usage.released: the authorisation hold was refunded.
  • usage.test: a test event sent from the settings panel.

Example payload (field names mirror GET /usage/runs/{run_id}):

{
"event": "usage.settled",
"event_id": "8f3d2c10-4f5a-4b1e-9c6d-2e7a1b0f3a11",
"job_id": "11111111-1111-4111-8111-111111111111",
"run_id": "11111111-1111-4111-8111-111111111111",
"thread_id": "22222222-2222-4222-8222-222222222222",
"team_id": "33333333-3333-4333-8333-333333333333",
"billed_team_id": "33333333-3333-4333-8333-333333333333",
"user_id": "44444444-4444-4444-8444-444444444444",
"source": "public_agents_api",
"budget": "high",
"status": "settled",
"requested_credits": 0.25,
"charged_credits": 0.184321,
"tokens": {"prompt": 1200, "completion": 340},
"team_remaining": {"free_remaining": 0.0, "paid_remaining": 997.358889, "remaining": 997.358889},
"settled_at": "2026-08-20T15:04:12+00:00",
"created_at": "2026-08-20T15:04:00+00:00"
}

Each request carries X-Coda-Signature: t=<unix>,v1=<hmac-sha256-hex>, X-Coda-Event, and X-Coda-Delivery-Id. Verify before trusting:

import hashlib, hmac, time
sig = dict(part.split("=", 1) for part in request.headers["X-Coda-Signature"].split(","))
signed = f"{sig['t']}.".encode() + request.body # raw request bytes, not re-serialized JSON
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if abs(time.time() - int(sig["t"])) > 300:
raise ValueError("Stale callback timestamp")
if not hmac.compare_digest(expected, sig["v1"]):
raise ValueError("Invalid callback signature")

Deliveries retry up to 20 attempts with exponential backoff starting at 30 seconds and capped at 1 hour (roughly 13 hours total). Deduplicate on event_id. Retries replay the same delivery. A debt event may be followed by a second usage.settled for the same run_id once the debt is collected; use the latest settled_at / charged_credits. status and charged_credits are snapshotted when the event is enqueued. team_remaining reflects the wallet at delivery time.

The usage pull API remains the source of truth. Callbacks are a notification, not the ledger.

user_id on the payload is the actor. API-token identity is not stored.