Skip to main content

Event log

hosting-service records every catalog event in one table, EventLog, whether or not a webhook rule exists for it. Django admin shows the rows under Hosting > Log events.

The same events can also be delivered as webhooks. Retention for both is the hourly purge in Scheduled jobs.

Row format​

ColumnContent
idSequential id
company_idIdentity company id the event belongs to
user_idIdentity user id who created the app, deployed, or deleted the app
event_typeCatalog event type
dataEvent payload, compacted as described below
created_atWhen the event happened

data is the webhook payload with empty values removed (null, empty strings, empty lists, and false). company_id and user_id are removed from data because they are columns. actor_email is added when the request token carried an email.

hosting-service has no user or company tables. Ids and emails come from the identity-service JWT. Deployment succeeded and failed events name the caller who finalized the deployment.

Two indexes serve listing, the retention check, and the purge: (company_id, created_at) and (company_id, user_id, created_at).

Retention​

Retention is one setting for the whole service: EVENT_LOG_RETENTION_DAYS (default 7).

Schedule purge_expired_data every hour. It deletes events older than the retention, together with delivered and dead webhook deliveries, in short batches:

python manage.py purge_expired_data
python manage.py purge_expired_data --dry-run
python manage.py purge_expired_data --max-seconds 300

--dry-run counts rows and deletes nothing. --max-seconds 300 stops after 300s. The next run continues. The container runs this every hour at minute 17. If events older than the retention plus one day are still stored, the job is not running. The admin dashboard and Hosting > Log events then show an error that asks you to check it. See Scheduled jobs.

Admin API​

Callers must be Django staff or a company owner, with a Bearer JWT from identity-service. Staff may pass any company_id query parameter. Company owners are limited to the JWT company_id. The same value is accepted. Another company returns 403.

MethodPathPurpose
GET/api/v1/actions/event-logNewest first, paginated
GET/api/v1/actions/event-log/{id}One event
GET/api/v1/actions/event-log/typesEvent types with label and description
GET/api/v1/actions/event-log/retentiondata_retention_days, oldest_event_at, stale_events

Filters on GET /api/v1/actions/event-log:

ParameterMeaning
event_typeOne type, or several separated by commas
user_idIdentity user id
userCase-insensitive part of the actor email
created_afterISO 8601 datetime, inclusive
created_beforeISO 8601 datetime, exclusive
page, page_sizePagination. page_size up to 100, default 20

Rows have the same shape as identity-service GET /api/v1/events:

{
"id": 87,
"company_id": 1,
"created_at": "2026-10-02T09:14:03.120Z",
"event_type": "hosting.deployment.failed",
"label": "Deployment failed",
"user_id": 42,
"user_email": "ada@example.com",
"data": {
"app_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "docs",
"slug": "vpzzsxvzsmp7",
"display_name": "Docs",
"deployment_id": "660e8400-e29b-41d4-a716-446655440001",
"app_version": "1.4.0",
"shellui_version": "0.5.0",
"status": "failed",
"error": "artifact_extract_failed",
"actor_email": "ada@example.com"
}
}

shellui_version in that sample is the version string stored on the deployment. It is not the hosting-service release.