# How Thunderbolt Handles Account Deletion and Device Revocation Requests

> Learn how Thunderbolt handles account deletion and device revocation. Discover the two-stage reset flow and HTTP status codes that ensure a clean signed-out state on the frontend.

- Repository: [Thunderbird/thunderbolt](https://github.com/thunderbird/thunderbolt)
- Tags: how-to-guide
- Published: 2026-04-19

---

**Thunderbolt implements a two-stage reset flow where backend endpoints return specific HTTP status codes (410 Gone for deleted accounts, 403 Forbidden for revoked devices) that trigger a frontend listener to clear local data and force a full page reload, ensuring every client reaches a clean signed-out state.**

The Thunderbird Thunderbolt project manages sensitive user state transitions through a coordinated architecture spanning REST endpoints, PowerSync token services, and React hooks. Understanding how Thunderbolt handles account deletion and device revocation requests requires examining the specific HTTP status codes, custom browser events, and database cleanup routines that ensure consistency across distributed clients.

## Backend Endpoints and Token Service Logic

### Account Deletion and Device Revocation Endpoints

The backend exposes dedicated endpoints for permanent account removal and device management. In [`backend/src/api/account.ts`](https://github.com/thunderbird/thunderbolt/blob/main/backend/src/api/account.ts), the implementation handles:

- **`DELETE /v1/account`**: Permanently removes the user and all related data via hard delete, returning `200` on success.
- **`POST /v1/account/devices/:id/revoke`**: Marks a specific device as revoked by setting a `revoked_at` timestamp, returning `204` on success.

### PowerSync Token Endpoint Validation

The critical validation logic resides in the **`GET /powersync/token`** endpoint. This handler checks the `X-Device-ID` header against the database to determine device and account status:

- **410 Gone**: Returned when the user no longer exists (account deleted), with code `ACCOUNT_DELETED`.
- **403 Forbidden**: Returned when the device row contains a `revoked_at` timestamp, with code `DEVICE_DISCONNECTED`.
- **401 Unauthorized**: Returned for generic authentication failures.

These status codes serve as the canonical signals that trigger client-side reset procedures, as documented in [`docs/delete-account-and-revoke-device.md`](https://github.com/thunderbird/thunderbolt/blob/main/docs/delete-account-and-revoke-device.md).

## PowerSync Connector and Credentials Invalid Detection

### Response Interception and Event Dispatch

The frontend PowerSync connector, located in [`src/db/powersync/connector.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/connector.ts), intercepts non-OK responses from the token endpoint and converts HTTP status/body pairs into typed reasons:

```typescript
// src/db/powersync/connector.ts
export const powersyncCredentialsInvalid = 'powersync_credentials_invalid';
export type CredentialsInvalidReason =
  | 'account_deleted'
  | 'device_revoked'
  | 'device_id_taken'
  | 'device_id_required';

```

The helper function `handleCredentialsInvalidIfNeeded` (lines 23-45) evaluates responses and dispatches a custom browser event when a reset reason is detected:

```typescript
if (reason) {
  window.dispatchEvent(
    new CustomEvent(powersyncCredentialsInvalid, { detail: { reason } })
  );
}

```

This event-driven architecture decouples the network layer from the UI reset logic, allowing multiple components to react to credential invalidation.

## Frontend Reset Flow and Listener Implementation

### The Central Credentials Invalid Listener

The hook `usePowerSyncCredentialsInvalidListener` in [`src/hooks/use-powersync-credentials-invalid-listener.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/hooks/use-powersync-credentials-invalid-listener.ts) provides the single implementation for handling account deletion and device revocation. It subscribes to two distinct data sources:

1. **Event-driven path**: Listens for the `powersync_credentials_invalid` custom event.
2. **Table-driven path**: Watches the synced `devices` table for local changes indicating `revoked_at` or row deletion.

The hook must be mounted **inside `AuthProvider`** before any early-return conditional rendering to ensure it remains active even after the database wipe triggers component unmounting.

### Performing the Reset

When triggered, the listener executes `performCredentialsInvalidReset` (lines 17-20), which orchestrates the two-stage cleanup:

1. **`clearLocalData()`**: Defined in [`src/lib/cleanup.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/lib/cleanup.ts), this function disables PowerSync synchronization, clears `localStorage` (removing the auth token and device ID), and deletes the local SQLite database.
2. **`window.location.replace`**: Performs a full page reload to a clean signed-out route (`/` or `/account-deleted`), ensuring no stale React state persists.

This guarantees that every client—whether actively syncing or idle—reaches a consistent signed-out state without manual user intervention.

## End-to-End Flow Examples

### Account Deletion Scenario

When a user deletes their account through the settings interface:

```typescript
// Example: Delete account button handler
async function deleteAccount() {
  const token = localStorage.getItem('auth_token');
  await fetch(`${process.env.REACT_APP_BACKEND_URL}/v1/account`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${token}` },
  });
  // Backend hard-deletes the user; other devices receive 410 on next token request
}

```

Other active devices detect the deletion on their next PowerSync token refresh, receive the `410 Gone` response, and trigger the full reset flow.

### Device Revocation Scenario

When a user revokes a specific device from another session:

```typescript
// Example: Revoke another device by its UUID
async function revokeDevice(deviceId: string) {
  const token = localStorage.getItem('auth_token');
  await fetch(
    `${process.env.REACT_APP_BACKEND_URL}/v1/account/devices/${deviceId}/revoke`,
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${token}` },
    }
  );
  // The revoked device will see `revoked_at` via PowerSync sync or a 403 response
}

```

The revoked device immediately sees the `revoked_at` timestamp in its local `devices` table (triggering the table-driven reset) or receives a `403 Forbidden` on its next API call (triggering the event-driven reset).

### Using the Credentials Invalid Listener

Implement the reset handling in your application root:

```typescript
import { AuthProvider } from '@/contexts';
import { usePowerSyncCredentialsInvalidListener } from '@/hooks/use-powersync-credentials-invalid-listener';

function App() {
  usePowerSyncCredentialsInvalidListener(); // must be called inside AuthProvider
  return (
    <AuthProvider>
      {/* ...rest of the component tree */}
    </AuthProvider>
  );
}

```

## Key Implementation Files

Understanding how Thunderbolt handles account deletion and device revocation requires examining these specific source files:

- **[`src/db/powersync/connector.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/connector.ts)** – Intercepts HTTP 410/403 responses from the PowerSync token endpoint and dispatches the `powersync_credentials_invalid` event.
- **[`src/hooks/use-powersync-credentials-invalid-listener.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/hooks/use-powersync-credentials-invalid-listener.ts)** – Central React hook that listens for credential invalidation events and table changes, orchestrating the full reset flow.
- **[`docs/delete-account-and-revoke-device.md`](https://github.com/thunderbird/thunderbolt/blob/main/docs/delete-account-and-revoke-device.md)** – High-level design documentation explaining the reset policy, backend response codes, and client behavior.
- **[`backend/src/api/account.ts`](https://github.com/thunderbird/thunderbolt/blob/main/backend/src/api/account.ts)** – Backend implementation of the account deletion and device revocation REST endpoints.
- **[`src/lib/cleanup.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/lib/cleanup.ts)** – Contains `clearLocalData()`, which disables synchronization and wipes local storage and SQLite databases.

## Summary

Thunderbolt guarantees clean client state during account deletion and device revocation through a coordinated three-layer architecture:

- **Backend enforcement** via specific HTTP status codes (`410 Gone` for deleted accounts, `403 Forbidden` for revoked devices) returned by the PowerSync token endpoint.
- **Event abstraction** where the PowerSync connector converts HTTP errors into browser events (`powersync_credentials_invalid`) with typed reasons (`account_deleted`, `device_revoked`).
- **Unified reset flow** executed by `usePowerSyncCredentialsInvalidListener`, which clears local data via `clearLocalData()` and performs a full page reload to ensure no stale state persists.

This design ensures that whether a user deletes their account or revokes a specific device, all affected clients immediately enter a consistent signed-out state without requiring manual intervention.

## Frequently Asked Questions

### What happens when a user deletes their account in Thunderbolt?

When a user deletes their account, the backend hard-deletes all user data via `DELETE /v1/account` and returns HTTP 200. Other devices that remain active will receive an HTTP 410 Gone response on their next PowerSync token request, triggering an automatic reset that clears local data and reloads the page to a signed-out state.

### How does a revoked device know it has been disconnected?

A revoked device detects its status through two mechanisms. First, the PowerSync connector in [`src/db/powersync/connector.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/connector.ts) intercepts HTTP 403 Forbidden responses with code `DEVICE_DISCONNECTED` from the token endpoint and dispatches a `powersync_credentials_invalid` event. Second, the `usePowerSyncCredentialsInvalidListener` hook monitors the local `devices` table for changes to the `revoked_at` column, triggering the same reset flow immediately upon detection.

### What is the purpose of the `powersync_credentials_invalid` event?

The `powersync_credentials_invalid` event decouples network error handling from UI reset logic. Defined in [`src/db/powersync/connector.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/connector.ts), this custom browser event carries a typed reason (`account_deleted`, `device_revoked`, etc.) that allows the `usePowerSyncCredentialsInvalidListener` hook to execute a consistent, application-wide reset without requiring individual components to parse HTTP status codes or manage cleanup logic.

### Where does the actual data cleanup occur when credentials become invalid?

The actual cleanup occurs in [`src/lib/cleanup.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/lib/cleanup.ts) within the `clearLocalData()` function. This utility disables PowerSync synchronization, removes the authentication token and device ID from `localStorage`, and deletes the local SQLite database. After `clearLocalData()` completes, the `usePowerSyncCredentialsInvalidListener` hook performs a full page reload via `window.location.replace` to ensure the application starts from a completely clean state without stale React context or cache.