# account.disconnected

A connected account stopped working or was removed.

### Intentional

Removed on purpose, such as `DELETE /v1/accounts/{id}` or a switch to the Free plan. Kept 1 hour (`gracePeriodEndsAt`), then deleted with no further event. Reconnecting within the hour restores the same `accountId`.

### Unintentional

The connection broke (token expired or revoked, access lost). The account stays inactive until reconnected, keeping its `accountId`. Removing it instead sends a second event with `intentional`.

### Immediate paths

An API removal emits at once with `disconnectionType: "intentional"`. A phone-side WhatsApp disconnect emits within seconds of Meta's notification, with `unintentional`.

### WhatsApp reason

`reason` carries Meta's explanation. Deliberate: `BUSINESS_DOWNGRADE`, `CHANGE_NUMBER`, `USER_RE_REGISTERED`. Idle number dropped by Meta: `PRIMARY_INACTIVITY` after about 14 days, `COMPANION_INACTIVITY` after about 30.

### Expired or revoked grants

A publish or analytics sync getting a definitive auth error deactivates the account. Publish tries a token refresh first, so the event means the refresh failed too. `reason` carries the platform's message.

### Reconnection flags

A dead token on an ads or blog call flags the account, and the posting account sharing its token (`needsReconnection: true`). Each flagged account emits once; a later deactivation emits again, then not until reconnected.

### Lead forms while disconnected

While a Facebook Page is flagged or deactivated, `GET /v1/ads/lead-forms/{formId}/leads` answers `409` with `code: "account_disconnected"`.

### Reconciliation

Anything else (a number deleted in WhatsApp Manager, a channel Meta silently stops serving) is caught by periodic reconciliation, not a notification. It lags minutes to about a day.

### Do not rely on its absence

Meta's notifications are best-effort. For WhatsApp, poll `GET /v1/whatsapp/number-info` or account health, which runs a live Meta link probe.

### Subscribing

A webhook only receives the events it lists, so add `account.disconnected` explicitly.




**Payload for `account.disconnected`:**

- **id** (required) `string`: Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery. It identifies the event only, never an account or other resource.
- **profiles**: `WebhookProfiles` - See schema definition
- **test** `boolean`: Always true when present: only a sample sent by POST /v1/webhooks/test with an event carries it. Real deliveries never do.
- **event** (required) `string`: No description - one of: account.disconnected
- **account** (required) `object`: 
  - **accountId** (required) `string`: The account's unique identifier (same as used in /v1/accounts/{accountId})
  - **profileId** (required) `string`: The profile's unique identifier this account belongs to
  - **platform** (required) `string`: No description
  - **username** (required) `string`: No description
  - **displayName** `string`: No description
  - **disconnectionType** (required) `string`: intentional: removed on purpose, for example with DELETE /v1/accounts/{accountId} or by a switch to the Free plan for the accounts over its limit. The account is deleted permanently 1 hour later unless the same account is reconnected within that hour.

unintentional: the connection broke (token expired or revoked, access lost on the platform). The account stays in Zernio, inactive, until it is reconnected (keeping its accountId) or removed. - one of: intentional, unintentional
  - **reason** (required) `string`: Human-readable reason for the disconnection
- **timestamp** (required) `string` (date-time): UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.