# Events ## Overview The Identity Platform uses an **event-driven architecture** built on Redis Streams. Events decouple the platform from products and enable reliable, scalable, idempotent communication. Each event is published to a Redis stream and includes: - **event_type**: A namespaced identifier (e.g., `user.created`, `authentication.login`) - **event_id**: A UUID that uniquely identifies THIS event occurrence - **correlation_id**: A UUID that ties this event to a request chain (e.g., a login request may trigger `user.updated`, `session.created`, `authentication.login_success`) - **timestamp**: ISO 8601 UTC - **version**: Schema version of this event (semver: `1.0.0`) - **data**: Event-specific payload (product-agnostic) - **metadata**: Correlation info, source, IP, etc. ## Event Structure ```json { "event_id": "uuid", "correlation_id": "uuid", "event_type": "user.created", "version": "1.0.0", "timestamp": "2024-01-15T10:30:00.000Z", "source": "identity-platform", "data": { "user_id": "uuid", "email": "user@acme.com" }, "metadata": { "ip": "192.168.1.1", "user_agent": "Mozilla/5.0...", "request_id": "uuid" } } ``` ## Event Types ### User Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `user.created` | `1.0.0` | New user registered | `user_id`, `email`, `phone`, `created_at` | | `user.updated` | `1.0.0` | User profile updated | `user_id`, `changed_fields[]`, `updated_at` | | `user.deleted` | `1.0.0` | User deleted (GDPR) | `user_id`, `deleted_at` | | `user.disabled` | `1.0.0` | User status → suspended/banned | `user_id`, `status`, `reason` | | `user.verified` | `1.0.0` | Email/phone verified | `user_id`, `channel` (email/phone), `verified_at` | ### Organization Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `organization.created` | `1.0.0` | New org created | `org_id`, `name`, `created_by` | | `organization.updated` | `1.0.0` | Org details updated | `org_id`, `changed_fields[]`, `updated_at` | ### Membership Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `membership.created` | `1.0.0` | User added to org | `user_id`, `org_id`, `role`, `status` | | `membership.updated` | `1.0.0` | Role/status changed | `user_id`, `org_id`, `changed_fields[]` | | `membership.removed` | `1.0.0` | User removed from org | `user_id`, `org_id`, `removed_by` | ### Authentication Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `authentication.login` | `1.0.0` | Successful login | `user_id`, `session_id`, `product_key`, `ip`, `device` | | `authentication.logout` | `1.0.0` | Successful logout | `user_id`, `session_id`, `product_key` | | `authentication.failed` | `1.0.0` | Failed login attempt | `user_id` (if known), `email`, `ip`, `reason` | | `authentication.mfa_required` | `1.0.0` | MFA step reached | `user_id`, `method` | | `authentication.mfa_verified` | `1.0.0` | MFA passed | `user_id`, `method` | ### Session Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `session.created` | `1.0.0` | New session | `session_id`, `user_id`, `product_key`, `session_type` | | `session.expired` | `1.0.0` | Session timed out | `session_id`, `user_id`, `reason` | | `session.revoked` | `1.0.0` | Session revoked | `session_id`, `user_id`, `product_key`, `reason` | ### Security Events | Event Type | Version | Trigger | Data | |-----------|---------|---------|------| | `security.event` | `1.0.0` | Any security event logged | `event_type`, `severity`, `user_id`, `ip`, `user_agent` | | `security.token_revoked` | `1.0.0` | Token blacklisted | `user_id`, `token_type`, `reason` | ## Event Properties ### idempotency Every event has a unique `event_id` (UUID). Consumers must deduplicate by `event_id`. This allows safe retry of failed deliveries without duplicate processing. ### correlation Events triggered by the same request share a `correlation_id`. Example: ``` correlation_id: req_abc123 ↓ triggers event: user.created (user_id=123) event: session.created (user_id=123, session_id=456) event: authentication.login (user_id=123, session_id=456) ``` ### versioning Event payloads are versioned with semver. When the schema changes in a backward-compatible way (adding optional fields), the version increments the PATCH. Breaking changes (removing fields, changing types) increment MAJOR. Consumers MUST check version before processing. ## Delivery Guarantees | Guarantee | Mechanism | |-----------|-----------| | **At-least-once** | Redis Streams with consumer groups | | **Deduplication** | event_id uniqueness | | **Ordering** | Per-stream ordering preserved | | **Durability** | Redis AOF persistence | | **Retry** | Consumer must ACK (XACK); unacked claimed by other consumers | | **DLQ** | Failed messages (max retries exceeded) routed to `events.dlq.stream` | ## Consumer Contract Products consuming events MUST: 1. Use consumer groups per product + service combination 2. Acknowledge (XACK) every processed message 3. Idempotently handle duplicates via `event_id` 4. Validate `version` before processing (reject unknown MAJOR versions) 5. Route failed messages to DLQ after max retries 6. Preserve correlation_id across their own downstream events ## Internal vs External Events | Channel | Audience | Persistence | Delivery | |---------|----------|-------------|----------| | **SecurityEvent** (DB table) | Internal admin/audit | Permanent (2 years) | Queryable via API | | **Redis Streams** | Products + internal services | 7 days | Consumer group | | **Audit log** (SecurityEvent) | Compliance | Permanent | Archive | The `SecurityEvent` model in PostgreSQL is the internal audit log. These are NOT the same as Redis Stream events but are correlated: ``` User logs in → SecurityEvent (DB: login_success, severity=info) [audit trail] → Redis Stream event (user.created, authentication.login) [product notifications] ``` ## Stream Names | Stream | Events | |--------|--------| | `events.user` | user.created, user.updated, user.deleted, user.disabled, user.verified | | `events.organization` | organization.created, organization.updated | | `events.membership` | membership.created, membership.updated, membership.removed | | `events.authentication` | authentication.login, authentication.logout, authentication.failed | | `events.session` | session.created, session.expired, session.revoked | | `events.security` | security.event, security.token_revoked | | `events.dlq` | Dead-letter queue for failed deliveries |