Skip to main content

Event log

identity-service records every catalog event and every sign-in in one table, EventLog, whether or not a webhook rule exists for it. Shellui admin shows it under Identity > Log events and on each user profile. Events are kept for the company's retention period, 7 days by default.

What is recorded​

  • Every webhook catalog event when it fires: account created or deleted, invitations, SCIM provisioning, group changes, SCIM tokens, magic link requests, and SCIM conflicts
  • Sign-ins, as two log-only event types:
Event typeWhen
identity.auth.login.succeededA user signed in with OAuth, SAML, magic link, or the Django admin login form
identity.auth.login.failedA sign-in was refused. The user is linked when it could be resolved

Sign-in events cannot trigger webhook rules: anonymous traffic can produce failed sign-ins, and forwarding them would let anyone flood your endpoints.

Scheduled job runs are recorded as two staff-only platform events, described in Platform events (staff only).

identity.user.updated is logged when a tracked profile field changes. changed_fields names those fields. A false value such as active: false is omitted from data (empty values are dropped) and is still present on the webhook body.

Row format​

ColumnContent
idSequential id
companyCompany the event belongs to. Empty for a sign-in that failed before the company was known, and for platform events
userUser the event is about, when there is one. Cleared when the user is deleted or removed from the company
event_typeCatalog event type
dataEvent payload, compacted (see below)
created_atWhen the event happened

To keep rows small, data is the webhook payload with:

  • empty values (null, empty strings and lists, false) removed
  • user_id removed, since it is the user column
  • secret fields removed: an event type can list fields that are never logged, sent to webhooks or stored. No webhook payload contains a sign-in link or token

Sign-in events store provider, failure_reason, is_staff_at_event, ip_hash (salted hash, never the raw IP), user_agent (truncated), client_timezone, client_device_id_hash, client_country and client_city (GeoIP, when configured).

The table has two indexes, (company, created_at) and (user, created_at). They serve every listing, the user timeline, the retention check, and the purge.

Retention​

Each company has a data retention in days (Company.data_retention_days, default 7). Only Shellui operators can change it, in Django admin under Companies > Data retention. Company owners see the value in the admin panel.

The purge_expired_data scheduled job deletes events older than the retention, together with finished webhook deliveries, email-service event posts, SCIM provisioning events, and scheduled job runs. It runs every hour inside the container when REDIS_URL is set.

If events older than retention + 1 day are still stored, the job is not running. The admin panel dashboard, the Log events page, and the Django admin company page then show an error. See Stale events warning.

Admin API​

Staff and company owners read the log with an access token or a personal access token, plus company_id, like the other admin endpoints.

MethodPathPurpose
GET/api/v1/eventsEvent log, newest first, paginated
GET/api/v1/events/{id}One event
GET/api/v1/events/typesEvent types with label, description and webhook (false for sign-ins). Platform events are not listed
GET/api/v1/events/retentiondata_retention_days, oldest_event_at, stale_events

GET /api/v1/events filters:

ParameterMeaning
event_typeOne type, or several separated by commas
user_idUser id
userCase-insensitive part of the user email (also matches data.email, for invitations)
created_afterISO 8601 datetime, inclusive
created_beforeISO 8601 datetime, exclusive
page, page_sizePagination, page_size up to 100 (default 20)
scopecompany (default) or platform (Django staff only, see Platform events)

Example row:

{
"id": 1042,
"company_id": 1,
"created_at": "2026-10-02T09:14:03.120Z",
"event_type": "identity.auth.login.failed",
"label": "Sign-in failed",
"user_id": 42,
"user_email": "ada@acme.com",
"data": {
"provider": "github",
"failure_reason": "Company access is disabled.",
"ip_hash": "b5bb9d80…",
"user_agent": "Mozilla/5.0 …"
}
}

Platform events (staff only)​

Scheduled jobs are platform maintenance, not company activity, so their runs are logged without a company and only Django staff can read them:

Event typeWhen
identity.scheduled_job.succeededA retry_webhooks or purge_expired_data run finished without error
identity.scheduled_job.failedA run failed

data holds run_id, job, trigger (celery or command), duration_ms, counts and host, plus error_key and error_class on failure. The run itself, with the sanitized error message, is at GET /api/v1/scheduled-jobs/runs/{run_id}. See Scheduled jobs monitoring.

These events never reach company owners or other companies:

  • GET /api/v1/events lists the company in the token only, so it never returns them
  • GET /api/v1/events?scope=platform and GET /api/v1/events/{id}?scope=platform return them to staff. Other callers get 403
  • GET /api/v1/events/types does not list them
  • they cannot trigger webhook rules and are never forwarded to email-service

They expire after the default retention of 7 days, like the runs.