Events reference — overview
ScaiControl emits a defined set of webhook events for every billing- and subscription-related state change. This page covers the envelope, signing, and delivery semantics. The full topic catalog with payload schemas is in Catalog.
Envelope#
Every event ships in the same outer envelope:
1 2 3 4 5 6 7 8 9 | |
| Field | Purpose |
|---|---|
event_id |
UUIDv4 unique to this delivery. Also echoed in X-ScaiControl-Event-Id header. |
event_type |
Unprefixed topic name (e.g. subscription.activated). The scaicontrol. prefix and .vN suffix are implicit in source + event_version. |
event_version |
Semver-ish MAJOR.MINOR. Breaking field changes bump MAJOR. |
occurred_at |
RFC 3339 UTC of when the underlying domain change happened — NOT when this delivery was attempted. |
source |
Always "scaicontrol". |
idempotency_key |
Stable across retries. Format: <resource_type>:<resource_id>:<event_type>:<lifecycle_step>. Use it for inbox dedup. |
data |
Per-topic payload. See Catalog for shapes. |
The full envelope JSON Schema is at https://www.scailabs.ai/docs/scaicontrol/reference/events/envelope-schema.
Headers#
POSTed to the subscriber's target_url:
1 2 3 4 5 6 | |
Signature verification#
1 2 3 4 5 | |
The secret is per-subscriber, configured at subscription registration. Use HMAC-SHA256 over the raw request body (not a parsed JSON re-serialisation — whitespace would differ).
Replay protection#
1 2 3 4 5 6 | |
A subscriber that gets a delivery whose X-ScaiControl-Timestamp is more than 5 minutes off should return 401. ScaiControl will retry; if it's still too old by the next attempt, that's a system-clock issue worth alerting on.
Delivery semantics#
At-least-once. Subscribers must dedup via event_id or idempotency_key (or both).
Retry schedule for 5xx / timeout / network errors:
| Attempt # | Delay before next retry |
|---|---|
| 1 (first) | (initial — no wait) |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
| 7 | 24 hours |
| After 7 attempts | Marked dead, no more retries |
Response code semantics (from the subscriber's reply):
| Code | Interpretation |
|---|---|
200 OK (or any 2xx) |
Accepted. Delivery row → dispatched. |
409 Conflict |
Idempotent ack (subscriber already saw it). Treated as success. |
400 Bad Request |
Malformed. Don't retry. Row → dead. Alerting recommended on the subscriber side. |
401 Unauthorized |
Signature or replay failure. Don't retry — this is config drift, not transient. Row → dead. |
| Other 4xx | Don't retry. Row → dead. |
503 Service Unavailable or other 5xx |
Retry with exponential backoff. |
| Timeout / network error | Same as 5xx. |
Subscriber inbox pattern#
The reference shape for the subscriber's inbox:
1 2 3 4 5 6 7 8 9 | |
Subscribe path:
- Receive POST.
- Verify signature + timestamp.
INSERT … ON CONFLICT DO NOTHINGon(event_id). If skipped, return 200 (or 409).- Return 200 immediately. Process the row asynchronously.
This pattern means slow processing never blocks ScaiControl's dispatcher — the 200 ack lets the next event in.
Versioning policy#
- Patch / minor field additions to a topic don't bump the version. Subscribers should ignore unknown fields.
- Breaking changes (field removal, type narrowing, semantic shift) ship the new shape under a new major (
v2). For at least one full quarter, bothv1andv2are emitted concurrently — subscribers migrate at their own pace, then we stop emittingv1. - Topic removal never happens silently. Deprecation notice → 3-month grace period → silent producer removal. Schemas stay published indefinitely so old fixtures still validate.
What's NOT in the launch catalog#
Topics deferred past MVP (see Catalog for the comparison with what consumers like ScaiCRM expect):
- Invoice events (
invoice.issued,invoice.paid,invoice.overdue). - Dunning escalation.
- Usage aggregates (daily, monthly).
- Catalog product events.
- Provisioning rejection.
subscription.renewed.v1— folded intosubscription.changed.v1withchange_kind="renewal".
These topics will arrive as the corresponding features ship.
Subscriber management#
Manage subscribers via the admin UI at /admin/webhook-subscriptions or via the API at /api/v1/admin/webhook-subscriptions — see Admin — webhook subscribers. Each subscription carries a name, target URL, list of topic patterns (glob), and an inline secret or vault path.
See also#
- Catalog — full topic list with payload shapes and sample payloads.
- Webhooks — operational page on the dispatcher itself.
- Concepts: webhooks — conceptual overview.