Skip to content

Webhook events

Rakomi dispatches webhooks from the same transaction that commits the state change, so a webhook is queued if and only if the state change itself succeeded. Delivery is eventual-consistent (queued, then retried 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). If you need delivery confirmation, read the delivery history for your webhook endpoint rather than inferring it from this 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).