Skip to content

Webhook events

Rakomi dispatches webhooks from the same transaction that commits the state change, so a webhook row is created if and only if the state change itself succeeded. Delivery is eventual-consistent (the outbox worker polls and retries with exponential backoff); consumers must be idempotent.

Publisher apps: the events below are the tenant webhook catalog. Third-party publishers consuming install-lifecycle events use the separate publisher-webhook receiver contract.

Fires on BOTH explicit and automatic account linking (the method field discriminates). The provider email is HMAC-SHA256 hashed with a tenant-scoped secret — raw emails never leave the event payload (GDPR Art. 25 data minimisation).

{
"id": "01J9...",
"event": "user.account_linked",
"severity": "info",
"timestamp": "2026-04-24T12:34:56.789Z",
"tenant_id": "c7f4...",
"data": {
"userId": "7a2e...",
"provider": "google",
"providerEmailHash": "4e3c9b...",
"linkedAt": "2026-04-24T12:34:56.789Z",
"method": "explicit_link",
"notificationSent": true
}
}
  • method'explicit_link' when the user clicked “Add account” in the dashboard, 'automatic_link' when the backend merged a verified-email provider identity onto an existing account during sign-in.
  • notificationSenttrue iff the account-linked notification email was enqueued in the same transaction as the link commit. false only in the defensive-fallback case where the enqueue INSERT failed but the link tx already committed (effectively impossible given our same-tx enqueue; documented as theoretical).

Note that notificationSent: true is a “notification was queued” semantic, NOT a “delivered” semantic. Delivery SLO is 15 minutes (OWASP ASVS V2.7.3). Tenants that need delivery confirmation should subscribe to the OutboxWorker status via the admin API (out of scope for the public webhook event).

{
"id": "01J9...",
"event": "user.account_unlinked",
"severity": "info",
"timestamp": "2026-04-24T12:34:56.789Z",
"tenant_id": "c7f4...",
"data": {
"userId": "7a2e...",
"provider": "google",
"unlinkedAt": "2026-04-24T12:34:56.789Z"
}
}

Unlink is the de-escalation path — a user who mis-linked can always reverse it, even during the post-link cooldown window. If the provider is Apple and a refresh token was persisted, Rakomi calls the Apple revocation endpoint best-effort in the background; the result lands in the audit log (apple_revocation_status: 'success' | 'failed' | 'no_token') but NOT in the webhook payload.

Fires when a Tenant Owner changes an OAuth client’s registered scopes via the dashboard (PATCH .../oauth-clients/{clientId}). Emitted only on a real transition — an identical-scope PATCH (order/duplicate-insensitive, case-sensitive) is a no-op and dispatches nothing.

{
"id": "01J9...",
"event": "oauth_client.scopes_updated",
"severity": "info",
"timestamp": "2026-07-01T12:34:56.789Z",
"tenant_id": "c7f4...",
"data": {
"scope_change_id": "3f1a...",
"client_id": "client_abc123",
"previous_scopes": ["openid", "profile", "email"],
"new_scopes": ["openid", "email", "sso:read"],
"added_scopes": ["sso:read"],
"removed_scopes": ["profile"],
"change_reason": "We now read your SSO configuration.",
"updated_at": "2026-07-01T12:34:56.789Z",
"updated_by": "{\"user_id\":\"7a2e...\"}"
}
}

Delta-shape semantics — read these carefully to stay redelivery-safe:

  • new_scopes is the authoritative full scope set. Prefer it over replaying added_scopes/removed_scopes; at-least-once redelivery or reordering from the outbox is then self-correcting.
  • previous_scopes / new_scopes are canonical; added_scopes (new − previous) and removed_scopes (previous − new) are convenience-derived.
  • removed_scopes is informational — a requested-scope change, NOT a revocation. Tokens already issued on the old scopes keep working until natural expiry (RFC 7662 reports token-time scope). To revoke immediately, use the token/session revocation path.
  • scope_change_id is a stable, per-change UUID — use it as the dedupe key (updated_at alone is insufficient).
  • updated_by is a JSON-encoded object ({"user_id":"..."}) — id-only on the wire for PII minimization (the actor email stays in the tenant’s internal audit log). The object shape is forward-compatible: a future non-human actor gains a type discriminator, never a scalar replacement. (This mirrors the changes field on role.updated, which is likewise JSON-encoded.)

Governing specs: RFC 6749 §3.3 (scope tokens, case-sensitivity, set semantics), §4.1.2.1 (state echo on the consent decline); RFC 9700 (incremental auth, exact redirect matching); OIDC Core 1.0 §3.1.2.6 (consent_required under prompt=none); RFC 8414 §2 (scopes_supported); RFC 7662 / RFC 7009 (introspection/revocation report token-time scope — removal ≠ revocation).