Account Deletion Cascade
Deleting a ThreatWinds user account triggers a cross-service cleanup. This page documents exactly what happens, in what order, across authentication, billing, and compute services, so consumers know which resources survive and which are swept up.
Per-role behavior (when a user is deleted)
When a user is deleted — either via DELETE /auth/v2/user (self) or DELETE /auth/v2/admin/user/{id} (Admin API) — the billing service iterates every customer the user was a member of and applies one of three paths, independently per customer:
| User’s role on this customer | Other owners exist? | Action |
|---|---|---|
| Owner | Yes | Only the user’s Role row is removed. The customer, its subscription, other members, and all their compute instances are untouched. The deleted user’s own compute instances are still cascade-deleted (see below). |
| Owner | No (last owner) | Full customer cascade. Payment provider customer is deleted (subscription cancellation is a side effect), all Role rows are deleted, the Customer row is deleted, customer usage data is cleared, and a customer.deleted event fires which wipes every compute instance under the customer. |
| Admin or User | n/a | Only the Role row is removed. The deleted user’s own compute instances are still cascade-deleted. |
Memberships are evaluated independently: a user who is last-owner of customer A and plain User of customer B gets the full cascade on A and only a role removal on B, from a single deletion event.
Scenario walkthroughs
Scenario 1 — Non-owner member with compute instances
A user holds User or Admin role on one customer and owns 3 compute instances.
On DELETE /auth/v2/user:
- The user is hard-deleted (sessions (Session API), emails, and API keys (Keypair API)).
user.deletedis emitted.- Billing removes the user’s Role row. Customer intact.
- Compute deletes the user’s 3 instances and reconciles the customer’s quota counter to the remaining fleet count (best-effort).
Final state: user gone, 3 instances gone, customer and its other members unchanged.
Scenario 2 — Owner with co-owners
A user is Owner of a customer with another Owner on the same account.
- The user is hard-deleted and
user.deletedis emitted. - Billing sees “owner but not last owner” — removes the Role row only. The other owner retains full control.
- Compute still cascade-deletes the deleted user’s own instances (if any).
Final state: customer persists under the surviving owner; only the deleted user’s personal resources are removed.
Scenario 3 — Last owner deletion
A customer has 1 Owner, 2 Admins, and a mix of compute instances. The Owner’s account is deleted.
- The user is hard-deleted and
user.deletedis emitted. - The billing service processes the deletion, sees “last owner”, and runs the full cascade:
- Payment provider customer is deleted — cancels the subscription as a side effect.
- All Role rows for the customer are deleted.
- The Customer row is deleted.
- A
customer.deletedevent is published.
- The compute service independently processes
user.deletedandcustomer.deletedevents. Onuser.deleted, it deletes the deleted user’s own instances. Oncustomer.deleted, it removes all instances for the customer (no ownership check — this includes the Admins’ instances).
Final state: customer gone, every instance gone, Admins lose access to the customer but their own accounts survive.
Scenario 4 — Admin force-deletes a user who is last owner
Identical cascade to Scenario 3. The entry point is DELETE /auth/v2/admin/user/{id} instead of the user’s own DELETE /user; the event is attributed to the admin who initiated the deletion.
Scenario 5 — Owner calls DELETE /billing/v1/customer
Note: The caller must be OWNER on ALL of their customer memberships. If the user has any non-owner role on any customer, the request is rejected with 403 before any cascade fires.
The owner deletes the customer directly (without deleting their own user account):
- The billing service runs the same cascade as Scenario 3 (payment provider delete, Role rows, Customer row, usage data cleared,
customer.deletedemitted — the cascade is marked as owner-initiated). - The owner’s account is NOT touched; they continue to exist as a user, now unaffiliated.
- The compute service processes the deletion event and wipes every instance under the customer.
Scenario 6 — billing_admin force-deletes a customer
DELETE /api/billing/v1/admin/customer/{id} is gated by the billing_admin role. Identical cascade to Scenario 5; the cascade is marked as admin-forced and attributed to the admin who initiated the deletion. No user account is touched.
Scenario 7 — compute_admin force-deletes an instance
DELETE /api/compute/v1/admin/instances/{id} is gated by the compute_admin role. Synchronous: the instance is deleted and the quota counter is released against the instance’s customer. A quota release call is made to the billing service. No lifecycle events fire. 204 on success, 404 if the instance does not exist.
Scenario 8 — User disabled (admin action)
When a user is disabled by an admin, the user.disabled event fires. Compute stops all RUNNING instances for the user (best-effort). STAGING and PROVISIONING instances are NOT stopped; only RUNNING instances are affected. Instances are NOT deleted; the user’s compute resources and billing relationship remain intact. If the user is re-enabled, instances can be resumed.
Scenario 9 — Subscription downgrade
When a customer’s subscription tier or quantity changes such that effective quota shrinks, the subscription.changed event fires. The effective quota is calculated as tier_base × quantity. If the new effective quota is not less than the old effective quota, no instances are deleted (unused capacity is preserved). Otherwise, compute deletes only instances that exceed the new quota cap (newest first). If the customer already has fewer instances than the new cap, no instances are deleted.
Effective quota formula: effective_quota = tier_base × quantity. For example, a tier with base quota 3 and quantity 2 yields an effective quota of 6 instances.
Scenario 10 — Subscription cancelled
When a customer’s subscription is cancelled (subscription.cancelled event), the effective quota drops to zero. Compute deletes ALL instances for the customer, regardless of ownership. This fires as a side effect of payment provider subscription cancellation, customer deletion, or manual billing cancellation.
Final state: customer subscription is cancelled, all instances removed, customer record persists (unless the customer itself was also deleted).
Idempotency
All deletion operations are idempotent. Calling a deletion endpoint against an already-deleted resource returns 404 without side effects.
Best-Effort Guarantee
Compute lifecycle event handlers are best-effort. Pub/sub messaging has no redelivery guarantee. If an event is missed, an instance may be left orphaned. Use periodic reconciliation to catch discrepancies.