Platform events
When something changes on the platform — a user registers, is suspended, has roles changed — it’s published as a platform event your application can react to.
How events flow
Section titled “How events flow”- The auth API writes the event to a transactional outbox in the same database transaction as the change (so events can never be lost or fabricated relative to the data).
- A dispatcher publishes outbox rows to a RabbitMQ topic exchange (
arrowlabs.events), using the event type as the routing key. - Each application has its own durable queue,
arrowlabs.events.{client_id}, bound to the event types it subscribes to. Your consumer drains that queue.
You consume only — the platform owns the queue and bindings. The SDKs attach to your queue without declaring or binding anything.
The envelope
Section titled “The envelope”Every message is a JSON envelope (snake_case on the wire):
{ "event_id": "…", "event_type": "user.registered", "event_version": 1, "occurred_at": "2026-06-20T10:30:00Z", "organisation_id": "…", "actor_user_id": "…", "data": { /* event-specific payload */ }}The SDKs decode this and hand your handler the typed data payload plus the envelope metadata.
The event catalogue
Section titled “The event catalogue”| Event type | Payload highlights |
|---|---|
user.registered | user_id, email, display_name, roles |
user.profile.updated | user_id, changed_fields, current_profile |
user.email.changed | user_id, previous_email, new_email |
user.suspended / user.unsuspended | user_id (+ reason) |
user.roles.changed | user_id, previous_roles, current_roles |
user.deleted | user_id, deleted_by |
user.application_access.granted / .revoked | user_id, application_id |
user.signed_in | user_id, client_ip, user_agent, mfa_used, method |
user.session.revoked | user_id, session_id, reason |
application.suspended / .unsuspended / .deleted | application_id |
Delivery guarantees
Section titled “Delivery guarantees”- At-least-once. Redelivery happens (reconnects, partial failures), so make your handlers idempotent. Every envelope’s
event_idis a stable dedup key — both SDKs expose a pluggable deduplicator hook. - Dead-lettering. If a handler fails (or a message can’t be decoded), it’s dead-lettered rather than retried in a tight loop. Failed events are inspectable and retryable from the admin portal.
Do it in code
Section titled “Do it in code”- TypeScript:
createEventConsumer - .NET:
AddArrowLabsEventConsumer