B2BB2B LLM

Callbacks

Receive signed, idempotent server-to-server events and completed async results.

Callbacks

Configure a callback URL in Profile → Callbacks and async results. Model Gate sends HTTPS POST requests for enabled account notifications and completed native async inference requests. Callback delivery is independent of inference processing and runs through a dedicated durable Model Gate worker queue.

Callback URL and outbound security

Use a publicly reachable HTTPS endpoint. Callback URL validation is an outbound security boundary. Loopback, private, link-local, carrier-grade NAT, documentation/test, multicast, unspecified and reserved IP ranges are rejected. The hostname is validated when saved and again during delivery; redirects are revalidated. Do not point callbacks at internal services or redirects into private networks.

Common event envelope

Every callback uses the same envelope:

{
  "event_id": "01J...",
  "event": "account.balance_low",
  "occurred_at": "2026-08-28T07:00:00Z",
  "data": {}
}

event_id is stable for every retry of one logical event. Deduplicate deliveries by event_id; do not infer identity by comparing the rest of the payload. occurred_at is UTC RFC3339. Payloads may gain additional fields over time, so consumers must ignore unknown fields.

Every delivery also includes the same event ID in X-Model-Gate-Event-ID.

Event catalog

account.balance_low

{
  "event_id": "01J...",
  "event": "account.balance_low",
  "occurred_at": "2026-08-28T07:00:00Z",
  "data": {
    "balance": "10.1234567890",
    "threshold": "20.0000000000",
    "currency": "USD"
  }
}

key.spend_limit_threshold_reached

{
  "event_id": "01J...",
  "event": "key.spend_limit_threshold_reached",
  "occurred_at": "2026-08-28T07:00:00Z",
  "data": {
    "key_id": "KEY_PUBLIC_ID",
    "key_name": "Bank integration",
    "usage": "80.1234567890",
    "spend_limit": "100.0000000000",
    "threshold_percent": 80,
    "currency": "USD"
  }
}

group.spend_limit_threshold_reached

The payload uses group_id, group_name, exact-decimal usage, exact-decimal spend_limit, integer threshold_percent, and currency = "USD".

request.completed

{
  "event_id": "01J...",
  "event": "request.completed",
  "occurred_at": "2026-08-28T07:00:00.123456Z",
  "data": {
    "request_id": "01J...",
    "status": "completed",
    "response_status": 200,
    "response": {"id":"msg_...","type":"message"}
  }
}

response_status is nullable when no upstream HTTP status exists. Failed/canceled/expired async results may include data.error.

Financial values in callback payloads are exact decimal strings; they are not UI-rounded display values.

Threshold alerts are re-armed after the monitored value leaves its threshold condition. The threshold worker evaluates conditions approximately once per minute, outside the inference hot path, so threshold callbacks are not a real-time millisecond crossing signal.

Request format

POST /model-gate/callback HTTP/1.1
Content-Type: application/json
X-Model-Gate-Event-ID: 01J...
X-Model-Gate-Signature: t=1710000000,v1=hex_hmac_sha256

Signature verification

Calculate HMAC-SHA256 over the exact string <timestamp>.<raw_body> using the callback secret generated in Profile. The full secret is shown only when generated or rotated and is stored encrypted at rest. Compare the hexadecimal signature in constant time and reject timestamps outside your accepted replay window. The HMAC timestamp is specific to a delivery attempt; the event_id remains stable across retries.

Delivery, response, and retries

Any HTTP 2xx response accepts the event. HTTP 200 with an empty body is recommended. Transport errors, timeouts, and every non-2xx response are failures.

Each event has at most 6 attempts total: the initial attempt plus five retries. Failed attempts 1–5 are followed by 5 seconds, 30 seconds, 2 minutes, 10 minutes, and 1 hour. Each attempt has a hard 15-second overall timeout. After the sixth failed attempt the event becomes terminal failed and there is no further automatic retry. Stale worker claims are automatically returned to the durable queue.

Do not perform long-running work before responding. Verify the signature, persist/deduplicate by event_id, return 2xx, and process asynchronously.