Webhooks¶
Outbound POST to one URL per organization (http or https). Events mark a cache stale; they are not a feed of resolved entitlements and do not include usage or catalog CRUD.
If you cache: Webhooks. Configure: Console.
Headers¶
We POST JSON. HMAC is over the raw body.
| Header | Rule |
|---|---|
X-E10s-Webhook-Id |
Event id (ULID). Same on every retry. Dedup key. |
X-E10s-Webhook-Timestamp |
Unix seconds |
X-E10s-Webhook-Signature |
Hex HMAC-SHA256(secret, ${timestamp}.${rawBody}) |
entitlements.stale¶
This subscriber’s entitled values may have changed.
{
"id": "01H…",
"type": "entitlements.stale",
"occurred_at": "2026-03-01T09:00:00.000Z",
"organization_id": "…",
"subscriber_id": "cust_123",
"cause": "override",
"features": ["sso"]
}
| Field | Rule |
|---|---|
id |
ULID. Same on every retry. Dedup key. |
occurred_at |
Canonical UTC (YYYY-MM-DDTHH:mm:ss.sssZ) |
organization_id |
Your vendor org |
subscriber_id |
The subscriber whose snapshot to drop |
cause |
subscription | override | expiry | subscriber |
features |
Present when we know the keys. Omitted = drop the whole snapshot |
No allowed, limit, or source.
plan.grants.updated¶
This plan’s grants changed. One POST. No subscriber id list.
{
"id": "01H…",
"type": "plan.grants.updated",
"occurred_at": "2026-03-01T09:00:00.000Z",
"organization_id": "…",
"key": "pro"
}
Drop every cached snapshot, or those tagged with this plan key. Refill on next use.
Canceled subscribers already lost plan grants; they are not implied by this event. They still get override and expiry events.
When we fire¶
After the write commits, only if resolution could change. No event on 4xx. Check and GET never emit. Write 2xx does not wait for delivery.
| Write | Event |
|---|---|
POST …/subscriptions |
entitlements.stale cause: subscription — omit features |
POST …/subscriptions/change |
one entitlements.stale cause: subscription — not cancel+create |
POST …/subscriptions/{id}/cancel |
entitlements.stale cause: subscription |
PUT …/overrides/{feature} when kind/allowed/limit or current effectiveness changes |
entitlements.stale cause: override features: [feature] |
PUT identical override (same kind/allowed/limit; expires_at only moves further into the future, or null↔future with the same value) |
none |
PUT that sets expires_at in the past, or changes the live value |
entitlements.stale cause: override |
DELETE …/overrides/{feature} |
entitlements.stale cause: override features: [feature] |
Override expires_at is reached |
entitlements.stale cause: expiry features: [feature] |
DELETE …/subscribers/{id} |
entitlements.stale cause: subscriber — omit features (refetch 404) |
PATCH …/plans/{key} grants actually differ |
plan.grants.updated |
PATCH plan name only, or grants identical |
none |
POST plan / DELETE plan |
none — create has nobody on it; delete is 409 if any subscription exists |
| Subscriber create / rename | none |
| Feature create / rename / delete | none — create entitles nobody; delete is 409 if referenced |
| GET entitlements / POST check | none |
Check ignores an expired override at expires_at even if the expiry event has not been delivered.
Delivery¶
- One URL per organization. No URL, or disabled → we enqueue nothing.
- At-least-once.
idis stable across retries. Your 2xx = done. Timeout 5s. - Retry offsets from first attempt: 0, 30s, 5m, 30m, 2h, 5h, 10h, 24h (8 attempts). Then stop.
- 8 consecutive events that exhaust retries → disabled. A 2xx resets that counter. Enable in the console turns it back on.
Config¶
e10s.io → Settings → Webhooks. Console session with org:read / org:write. Incoming POSTs authenticate with HMAC, not X-API-Key.
Plaintext secret (e10s-wh-1-…) is only in create and rotate responses. GET returns secret_prefix (first 12 characters), never the secret.
| Method | Path | Scope | Result |
|---|---|---|---|
| GET | …/webhook |
org:read |
200 { url, secret_prefix, disabled, created_at, updated_at } · 404 if none |
| PUT | …/webhook |
org:write |
{ "url": "https://…" } create or replace URL. Create returns secret. URL change keeps the secret (no secret field). |
| POST | …/webhook/rotate |
org:write |
New secret (shown once). 404 if none |
| POST | …/webhook/enable |
org:write |
Clear auto-disable. 404 if none |
| DELETE | …/webhook |
org:write |
204. Later writes enqueue nothing. |
url must be http or https, no credentials, max 2048.