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.
Account linking
Section titled “Account linking”user.account_linked
Section titled “user.account_linked”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.notificationSent—trueiff the account-linked notification email was enqueued in the same transaction as the link commit.falseonly 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).
user.account_unlinked
Section titled “user.account_unlinked”{ "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.
OAuth clients
Section titled “OAuth clients”oauth_client.scopes_updated
Section titled “oauth_client.scopes_updated”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_scopesis the authoritative full scope set. Prefer it over replayingadded_scopes/removed_scopes; at-least-once redelivery or reordering from the outbox is then self-correcting.previous_scopes/new_scopesare canonical;added_scopes(new − previous) andremoved_scopes(previous − new) are convenience-derived.removed_scopesis 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_idis a stable, per-change UUID — use it as the dedupe key (updated_atalone is insufficient).updated_byis 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 atypediscriminator, never a scalar replacement. (This mirrors thechangesfield onrole.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).