> Markdown version of https://authpi.com/docs/concepts/events/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Events

Complete reference of all events emitted by AuthPI for webhooks, audit logs, and integrations.

AuthPI emits **events** whenever something significant happens in your account—a user signs up, a session is created, an API key is rotated, and so on. These events power [webhooks](/docs/guides/webhooks), audit logs, and analytics.

This page documents all event types, their payloads, and when they're triggered.

## Event Structure

All events follow the [CloudEvents 1.0](https://cloudevents.io/) specification with AuthPI-specific extensions.

```json
{
  "spec": "1.0",
  "id": "evt_01HN8K2X...",
  "type": "user.created",
  "source": "identities",
  "timestamp": 1705330953123,
  "subject": {
    "account_id": "acc_xxx",
    "issuer_id": "i_xxx",
    "user_id": "usr_xxx"
  },
  "auth": {
    "type": "api_key",
    "id": "key_xxx",
    "issuer": "i_xxx"
  },
  "data": {
    "user_id": "usr_xxx",
    "username_type": "email"
  }
}
```

### Fields

| Field | Type | Description |
|-------|------|-------------|
| `spec` | string | CloudEvents spec version (always `"1.0"`) |
| `id` | string | Unique event identifier |
| `type` | string | Event type (see categories below) |
| `source` | string | Service that emitted the event |
| `timestamp` | number | Unix timestamp in milliseconds |
| `subject` | object | Resource identifiers the event relates to |
| `auth` | object | Who or what triggered the event |
| `data` | object | Event-specific payload |

### Subject Fields

The `subject` object contains identifiers for filtering and routing:

| Field | Present When |
|-------|--------------|
| `account_id` | Always |
| `issuer_id` | Event relates to an issuer |
| `user_id` | Event relates to a user |
| `agent_id` | Event relates to an agent |
| `org_id` | Event relates to an organization |
| `client_id` | Event relates to a client |
| `session_id` | Event relates to a session |
| `api_key_id` | Event relates to an API key |
| `token_id` | Event relates to a personal token |
| `webhook_id` | Event relates to a webhook |

### Delivery via Webhooks

When an event is delivered to a [webhook](/docs/guides/webhooks), it is wrapped in a standard CloudEvents 1.0 envelope that differs slightly from the structure above:

- `subject` becomes a single string set to the most specific entity ID (e.g. the user ID for `user.created`, the org ID for `organization.deleted`), and all subject fields are merged into `data` alongside the event-specific payload.
- `timestamp` (Unix ms) is delivered as `time` (ISO 8601), and `spec` as `specversion`.
- `source` identifies the delivering webhook, and `auth` is not included.

See the [webhooks guide](/docs/guides/webhooks#payload-format) for the full delivered payload format.

### Auth Types

The `auth.type` field indicates what triggered the event:

| Type | Description |
|------|-------------|
| `user` | End user action (via portal or authenticated session) |
| `client` | OAuth client action |
| `api_key` | Core API call with API key |
| `token` | Core API call with personal token |
| `wallet` | x402 wallet-authenticated action |
| `system` | Automated system action (expiration, scheduled tasks) |
| `unauthenticated` | Public endpoint (signup, login) |
| `unknown` | Auth context could not be determined |

---

## User Events

Events related to user accounts and identity management.

Events emitted during hosted portal flows (signup, login, MFA) carry additional flow context in `data`: `flow_type` (`"portal_flow"`), `flow_id`, `ip_address`, `user_agent`, and `trace_id`. The payload examples below show only the event-specific fields.

### Lifecycle

| Event | Description |
|-------|-------------|
| `user.created` | New user account created |
| `user.updated` | User profile or settings updated |
| `user.deleted` | User account permanently deleted |
| `user.blocked` | User account blocked (too many failed logins or admin action) |
| `user.unblocked` | User account unblocked |

**`user.created` payload:**

```json
{
  "user_id": "usr_xxx",
  "username_type": "email"
}
```

For users created through a hosted portal signup or SSO just-in-time provisioning, the payload also includes signup attribution when available: `method` (`"password"` or `"sso"`), `ip_address`, `user_agent`, and — for portal signups — `flow_id`.

`user.created` fires whenever the user record is created, including signups gated behind [manual approval](#signup-approval) (those additionally emit `user.approval.requested`, and activation is signaled by `user.approval.approved`).

### Authentication

| Event | Description |
|-------|-------------|
| `user.verification.succeeded` | Credential verification succeeded |
| `user.verification.failed` | Credential verification failed |

### MFA

| Event | Description |
|-------|-------------|
| `user.mfa.factor-created` | MFA factor enrolled (TOTP, backup codes, etc.) |
| `user.mfa.verification.succeeded` | MFA verification succeeded |
| `user.mfa.verification.failed` | MFA verification failed |

MFA factor removal and backup-code regeneration are delivered as verifier events — `user.verifier.deleted` and `user.verifier.updated` respectively (see [Credentials](#credentials-verifiers) below).

**`user.mfa.verification.succeeded` payload:**

```json
{
  "verifier_type": "totp"
}
```

### Credentials (Verifiers)

| Event | Description |
|-------|-------------|
| `user.verifier.created` | New credential added (password, passkey, TOTP, etc.) |
| `user.verifier.updated` | Credential updated (currently fires when backup codes are regenerated) |
| `user.verifier.deleted` | Credential removed |
| `user.password.changed` | Password changed |
| `user.password.reset-requested` | Password reset email sent |
| `user.password.reset` | Password reset completed |

**`user.verifier.created` payload:**

```json
{
  "verifier_id": "ver_xxx",
  "verifier_type": "passkey"
}
```

For OAuth verifiers (social sign-in), the payload also includes `provider` (e.g. `"github"`) and `method_id` (the issuer auth method, `am_...`).

### Email Management

| Event | Description |
|-------|-------------|
| `user.email.verified` | Email address verified |
| `user.email.changed` | Email address changed |
| `user.email.change-requested` | Email change initiated (pending confirmation) |

### Magic Links

| Event | Description |
|-------|-------------|
| `user.login-link.requested` | Magic link login requested |
| `user.login-link.consumed` | Magic link used for authentication |

### Signup Approval

| Event | Description |
|-------|-------------|
| `user.approval.requested` | Signup requires manual approval — fires alongside `user.created`; the user starts in a pending state |
| `user.approval.approved` | Pending user approved and activated |
| `user.approval.rejected` | Pending user rejected and blocked |

**`user.approval.requested` payload:**

```json
{
  "user_id": "usr_xxx",
  "username_type": "email"
}
```

`signup_reason` (user-provided) and `triggered_rule` (the approval rule that matched) are included when set. Decisions carry `user_id` and `decided_by` (the deciding admin); rejections also include the `reason` shown to the user.

### Consent

| Event | Description |
|-------|-------------|
| `user.consent.granted` | User granted OAuth consent to a client |
| `user.consent.revoked` | Consent revoked for one client or for all clients |

**`user.consent.granted` payload:**

```json
{
  "consent_id": "cg_xxx",
  "client_id": "c_xxx",
  "scopes": ["openid", "profile", "email"]
}
```

Single-client revocations carry `client_id` and `scope: "single"`; revoke-all carries `revoked_count` and `scope: "all"`.

### Trusted Devices

| Event | Description |
|-------|-------------|
| `user.device.trusted` | Device trusted (skips MFA challenges until expiry) |
| `user.device.revoked` | Trusted device revoked (single device or all) |

**`user.device.trusted` payload:**

```json
{
  "device_id": "dev_xxx",
  "fingerprint": "5f4dcc3b5aa7...",
  "name": "MacBook Pro",
  "expires_at": 1708527753000
}
```

Single revocations carry `device_id`; revoke-all carries `revoked_count` and `revoke_all: true`.

### SSO Linking

| Event | Description |
|-------|-------------|
| `user.sso.provisioned` | New user provisioned just-in-time through organization SSO |
| `user.sso.linked` | Existing user linked to an SSO identity by email match |

Both carry the same payload:

```json
{
  "org_id": "org_xxx",
  "provider_user_id": "24f80cc5-52a1-4a5b-8f3e-9d7c1b2a3f4e"
}
```

`provider_user_id` is the `sub` claim from the organization's identity provider.

---

## Session Events

Events related to user sessions.

| Event | Description |
|-------|-------------|
| `session.created` | New session created and activated |
| `session.updated` | Session metadata updated |
| `session.token-refreshed` | Session tokens refreshed |
| `session.expired` | Session expired (TTL or idle timeout) |
| `session.terminated` | Session terminated. `data` carries `previous_status` and a short `reason` string such as `user_logout`, `security_event`, `session_limit_exceeded`, or `other`. |
| `session.compromised` | Session flagged as potentially compromised |

**`session.created` payload:**

```json
{
  "client_id": "c_xxx"
}
```

The session ID is in `subject.session_id`. Sessions created through a hosted portal sign-in also carry `session_id`, `device_trusted`, `portal_flow_type` (`login`, `signup`, or `password_reset`), and the portal flow context fields (`flow_id`, `ip_address`, `user_agent`, `trace_id`).

**`session.compromised` payload:**

```json
{
  "reason": "refresh_token_reuse"
}
```

`reason` is `refresh_token_reuse` or `device_fingerprint_mismatch`; the session ID is in `subject.session_id`.

This event triggers when AuthPI detects potential token theft, such as when a refresh token is used after it should have been rotated. All tokens for the session are immediately revoked.

---

## Organization Events

Events related to organizations and multi-tenancy.

### Organization Lifecycle

| Event | Description |
|-------|-------------|
| `organization.created` | Organization created |
| `organization.updated` | Organization settings updated |
| `organization.deleted` | Organization deleted |
| `organization.suspended` | Organization suspended (also fires `organization.updated`) |
| `organization.reactivated` | Suspended organization reactivated (also fires `organization.updated`) |

**`organization.created` payload:**

```json
{
  "name": "Acme Corporation",
  "org_type": "business",
  "created_by": "usr_xxx"
}
```

**`organization.suspended` payload:**

```json
{
  "status": "suspended",
  "previous_status": "active",
  "status_at": 1705935753000,
  "status_reason": "non-payment",
  "status_by": "usr_xxx"
}
```

When an organization is suspended, newly issued tokens (fresh logins, refreshes, `/userinfo`) stop carrying its claims. Use this event to revoke your own application sessions for the organization immediately, rather than waiting for access tokens to expire. `status_reason` and `status_by` are present only if they were set in the update.

### Memberships

| Event | Description |
|-------|-------------|
| `organization.membership.created` | User added to organization |
| `organization.membership.updated` | Membership scopes or settings updated |
| `organization.membership.deleted` | User removed from organization |

**`organization.membership.created` payload:**

```json
{
  "member_id": "usr_xxx",
  "scopes": ["member", "projects:read"],
  "groups": [],
  "user_title": "Developer"
}
```

`member_id` is a user (`usr_...`) or agent (`agt_...`) ID; the matching `user_id` or `agent_id` subject field is set accordingly.

### Invitations

| Event | Description |
|-------|-------------|
| `organization.invitation.created` | Invitation sent |
| `organization.invitation.accepted` | Invitation accepted |
| `organization.invitation.declined` | Invitation declined |
| `organization.invitation.deleted` | Invitation revoked (carries `reason: "revoked"`; expiry emits no event) |

**`organization.invitation.created` payload:**

```json
{
  "invite_id": "inv_xxx",
  "email_invited": "new.employee@example.com",
  "expires_at": 1705935753000
}
```

`inviter` (`{ id?, name? }`) is included when it was provided on the create request.

### Groups

| Event | Description |
|-------|-------------|
| `organization.group.created` | Group created |
| `organization.group.updated` | Group updated (scope changes re-sync member permissions) |
| `organization.group.deleted` | Group deleted (memberships re-synced) |

**`organization.group.created` payload:**

```json
{
  "group_id": "grp_xxx",
  "name": "Engineering",
  "scopes": ["projects:write"]
}
```

Updates carry `group_id` and `updated_fields` (the list of changed fields); deletes carry `group_id` and `name`.

### SSO Configuration

| Event | Description |
|-------|-------------|
| `organization.sso.enabled` | SSO enabled for the organization |
| `organization.sso.disabled` | SSO disabled |
| `organization.sso.updated` | SSO configuration changed while enabled (including secret rotation) |
| `organization.sso.domain-created` | SSO domain added, pending DNS verification |
| `organization.sso.domain-verified` | SSO domain ownership verified via DNS TXT record |
| `organization.sso.domain-deleted` | SSO domain removed |

`organization.sso.enabled` carries `protocol` (e.g. `"oidc"`); `organization.sso.updated` carries `updated_fields` (a secret rotation reports `["client_secret"]`); the domain events carry `domain` (normalized to lowercase). `organization.sso.disabled` has no payload.

---

## Agent Events

Events related to [agent identities](/docs/guides/agent-identities) (non-human principals).

| Event | Description |
|-------|-------------|
| `agent.created` | Agent created |
| `agent.updated` | Agent updated |
| `agent.deleted` | Agent deleted |
| `agent.verifier.added` | Agent credential added |
| `agent.verifier.removed` | Agent credential removed |

**`agent.created` payload:**

```json
{
  "name": "Support Triage Agent",
  "description": "Triages inbound support tickets",
  "model": "claude-sonnet-4-5",
  "provider": "anthropic"
}
```

The agent ID is in `subject.agent_id`. `agent.updated` carries the fields that changed; `agent.deleted` has no payload.

**`agent.verifier.added` payload:**

```json
{
  "verifier_id": "v_xxx",
  "verifier_type": "wallet"
}
```

`verifier_type` is `wallet` or `secret`; `agent.verifier.removed` carries the same fields.

---

## Client Events

Events related to OAuth clients (applications).

| Event | Description |
|-------|-------------|
| `client.created` | OAuth client registered |
| `client.updated` | Client configuration updated |
| `client.deleted` | Client deleted |
| `client.secret.rotated` | Client secret rotated |
| `client.blocked` | Client blocked |
| `client.unblocked` | Client unblocked |

**`client.created` payload:**

```json
{
  "name": "My Web App",
  "description": null,
  "type": "internal",
  "confidential": true,
  "protocol": "oidc"
}
```

The client ID is in `subject.client_id`.

**`client.secret.rotated`** carries no event-specific payload (`data` is `{}`) — the client, issuer, and account IDs are in `subject`, and the event `timestamp` is the rotation time.

### Machine Tokens

| Event | Description |
|-------|-------------|
| `machine.token.issued` | Access token issued via the `client_credentials` flow |

**`machine.token.issued` payload:**

```json
{
  "subject_type": "client",
  "scope_count": 3
}
```

`subject_type` is `client` (M2M client) or `agent`; `subject` carries the matching `client_id` or `agent_id`. The event fires on token issuance only, not on each use.

---

## API Key Events

Events related to API keys for programmatic access.

| Event | Description |
|-------|-------------|
| `api-key.created` | API key created |
| `api-key.updated` | API key settings updated |
| `api-key.deleted` | API key revoked |
| `api-key.blocked` | API key blocked |
| `api-key.unblocked` | API key unblocked |

**`api-key.created` payload:**

```json
{
  "name": "Production API Key",
  "scopes": ["users:read", "users:write"]
}
```

The key, organization, issuer, and account IDs are in `subject`.

---

## Personal Token Events

Events related to personal access tokens (user-owned).

| Event | Description |
|-------|-------------|
| `user.personal-token.created` | Personal token created |
| `user.personal-token.updated` | Personal token updated |
| `user.personal-token.deleted` | Personal token revoked |

**`user.personal-token.created` payload:**

```json
{
  "name": "CI/CD Token",
  "scopes": ["repos:read"]
}
```

The token and user IDs are in `subject.token_id` and `subject.user_id`.

---

## Issuer Events

Events related to issuer configuration.

| Event | Description |
|-------|-------------|
| `issuer.created` | Issuer created |
| `issuer.updated` | Issuer configuration updated |
| `issuer.deleted` | Issuer deleted |
| `issuer.auth-method.created` | Auth method added (OAuth provider, etc.) |
| `issuer.auth-method.updated` | Auth method configuration updated |
| `issuer.auth-method.deleted` | Auth method removed |

**`issuer.auth-method.created` payload:**

```json
{
  "issuer_id": "i_xxx",
  "method_id": "am_xxx",
  "method_type": "oauth2",
  "name": "Google"
}
```

---

## Account Events

Events related to your account: lifecycle, billing credits, and custom domains.

### Account Lifecycle

| Event | Description |
|-------|-------------|
| `account.created` | Account created |
| `account.updated` | Account changed (profile, features, limits, billing, payment methods, domains, suspension) |
| `account.deleted` | Account deleted |

The `account.updated` payload always carries `account_id` plus a field describing what changed — for example `changes` (the updated fields), `organization_changes`, `domain_added` / `domain_removed` / `domain_verified`, `primary_domain`, or `status` (`"suspended"` / `"active"`).

### Credits

| Event | Description |
|-------|-------------|
| `account.credits.updated` | Credits added (topup or bonus) or refunded |
| `account.credits.depleted` | Balance reached zero — fires once per depletion cycle, reset by the next topup |

**`account.credits.updated` payload:**

```json
{
  "account_id": "acc_xxx",
  "transaction_type": "topup",
  "entry_id": "cle_xxx",
  "amount_μusd": 10000000,
  "balance_after_μusd": 12500000,
  "operation": null,
  "reference": "pi_3OaXXXX"
}
```

`transaction_type` is `topup`, `bonus`, or `refund`. Bonus entries add `expires_at`; refunds add `related_entry_id` (the charge being refunded).

**`account.credits.depleted` payload:**

```json
{
  "account_id": "acc_xxx",
  "balance_μusd": -1200,
  "trigger_entry_id": "cle_xxx"
}
```

### Custom Domains

| Event | Description |
|-------|-------------|
| `account.domain.added` | Custom domain added to the account |
| `account.domain.removed` | Domain removed |
| `account.domain.assigned` | Verified domain assigned to an issuer |
| `account.domain.unassigned` | Domain unassigned from its issuer |

Payloads carry `account_id` and `host`; assignment events add `issuer_id`, and reassignments include `previous_issuer_id`. Domain verification itself is reported through `account.updated` with `domain_verified`.

---

## Webhook Events

Events related to webhook configuration (meta-events).

| Event | Description |
|-------|-------------|
| `webhook.created` | Webhook subscription created |
| `webhook.updated` | Webhook configuration updated |
| `webhook.deleted` | Webhook deleted |
| `webhook.paused` | Webhook manually disabled or auto-paused after persistent delivery failures |
| `webhook.resumed` | Disabled webhook reactivated |

---

## Email Events

Events related to email delivery.

| Event | Description |
|-------|-------------|
| `email.sent` | Email sent successfully |
| `email.failed` | Email delivery failed |

---

## Subscribing to Events

### Via Webhooks

Configure [webhooks](/docs/guides/webhooks) to receive events in real-time:

```bash
curl -X POST https://api.authpi.com/v1/accounts/{account_id}/webhooks \
  -u "{key_id}:{key_secret}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "User Events",
    "url": "https://your-server.com/webhooks/authpi",
    "events": ["user.created", "user.deleted", "session.compromised"],
    "auth": {
      "type": "signature",
      "signature_algorithm": "hmac-sha256"
    }
  }'
```

### Event Filtering

Webhooks subscribe to exact event names (e.g. `["user.created", "user.deleted"]`) — wildcard patterns are not supported. Unknown event names and internal-only event types (`exception` and `api-key.verified`, which never fan out to customer webhooks) are rejected when a webhook is created or updated. To narrow delivery to specific entities within your account, use [subject filters](/docs/guides/webhooks#subject-filters).

### Via API

Query historical events through the Core API:

```bash
curl "https://api.authpi.com/v1/accounts/{account_id}/events?type=user.created&limit=100" \
  -u "{key_id}:{key_secret}"
```

Filter parameters:

| Parameter | Description |
|-----------|-------------|
| `type` | Filter by event type |
| `source` | Filter by source service |
| `user_id` | Filter by user |
| `org_id` | Filter by organization |
| `since` | Events after timestamp |
| `until` | Events before timestamp |
| `limit` | Maximum results (default 50, max 1000) |

---

## Common Patterns

### Sync Users to External Systems

Subscribe to user lifecycle events to keep external systems in sync:

```json
{
  "events": ["user.created", "user.updated", "user.deleted"]
}
```

### Security Monitoring

Monitor for security-relevant events:

```json
{
  "events": [
    "user.verification.failed",
    "session.compromised",
    "user.blocked"
  ]
}
```

### Audit Logging

Capture all administrative actions:

```json
{
  "events": [
    "user.created",
    "user.updated",
    "user.deleted",
    "organization.created",
    "organization.updated",
    "organization.deleted",
    "client.created",
    "client.updated",
    "client.deleted",
    "issuer.updated"
  ]
}
```

### Billing Integration

Track usage-relevant events:

```json
{
  "events": [
    "user.created",
    "organization.created",
    "session.created"
  ]
}
```

---

## Event Delivery

Events are delivered asynchronously with at-least-once semantics. This means:

- Events may occasionally be delivered more than once
- Your webhook handler should be idempotent (use the event `id` to deduplicate)
- Events are typically delivered within seconds, but delays can occur during high load

For webhook retry behavior and failure handling, see the [Webhooks guide](/docs/guides/webhooks).

---

## Next Steps

- Set up [Webhooks](/docs/guides/webhooks) to receive events
- Learn about [Users](/docs/concepts/users) and their lifecycle
- Understand [Organizations](/docs/concepts/organizations) and membership events
- Review [API authentication](/docs/reference/) for querying events