Skip to content

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. id is 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.