AUTH4D-301 user.deleted webhook event

This fragment extends AUTH4D-158 (repository file docs/api-spec/auth4d-158.md) and AUTH4D-216 (repository file docs/api-spec/auth4d-216.md). It adds a sixth Flows event type, user.deleted, which webhook endpoints can subscribe to. No endpoint, signature header or migration changes: webhook_endpoints.event_types is a JSON array and fan-out matches types generically.

Event

FlowEventTypeSchema and FlowEventSchema in packages/contracts accept user.deleted. The envelope is the same as for every other event. data is strict:

Field Type Meaning
data.reason "user_request" | "inactive_guest" Why the lifecycle deletion job removed the user (DeletionReason).
{
  "event_id": "evt_Q2xk3n1y0b9VqT7mH4sR8uWcE6aJpLzN5dGfXiKoYv0",
  "version": 1,
  "type": "user.deleted",
  "tenant_id": "ten_0123456789abcdef",
  "application_id": null,
  "user_id": "usr_0123456789abcdef",
  "occurred_at": "2026-10-17T02:10:04.512Z",
  "context": {},
  "data": { "reason": "user_request" }
}

When it is published

  • The lifecycle deletion job (packages/lifecycle purgePhase) publishes it once, after the D1 batch that removes the user, identities, memberships, consent and session indexes has committed and completed the job. Only the worker whose fenced completion write succeeded publishes.
  • Nothing is published while a job is in the revoke phase, deferred by a late session revocation, failed, or cancelled. Orphaned-user cleanup (orphanedUserTask) publishes nothing.
  • application_id is always null and context is {}, like member.changed. Endpoints with an application filter therefore never receive user.deleted; only project-wide endpoints do.
  • event_id is derived from the tenant, the type and the deletion job id, so re-publishing the same job yields the same id and consumers dedupe on webhook-id.

Delivery

Delivery is best effort, as for every other event. LifecycleContext.events is optional; the API Worker wires it in createScheduledTaskDispatcher to publishFlowEvent. When the port is missing nothing is published. When it throws, the job still completes and a lifecycle.publish_failed log line carries only type and tenant_id. The deletion phases, the user.deleted audit event and the retention tasks are unchanged.

Console

The webhook form lists user.deleted as Player deleted and selects it by default for new endpoints.

Source: docs/api-spec/auth4d-301.md