Account status events
All 15 events that track the full lifecycle of a connected LinkedIn account: creation, sync, authentication changes, restriction, errors, removal, and initial history backfill.
Quick reference
The Availability column marks how each event is delivered. Events with no marker are
delivered in real time; account.restricted is normally detected by a daily poll, but
can also arrive immediately from a connect that observes the restriction, and four
legacy events are no_longer_realtime (not delivered on the current platform), see the
callouts below.
| Event | Availability | Description |
|---|---|---|
account.created | Real-time | A new account link completed successfully for the first time. |
account.connected | Real-time | A LinkedIn account is in a healthy, connected state, including when a connect reactivates an account you had previously disconnected. |
account.synced | no_longer_realtime | A synchronization cycle completed successfully. |
account.reconnected | Real-time | An account was re-authorized in place, credentials refreshed. |
account.reconnect_needed | Real-time | Credentials expired, the account requires re-authentication. |
account.restricted | not_realtime | LinkedIn restricted the account, actions on it will fail until the restriction is lifted. |
account.creation_failed | no_longer_realtime | The initial account-link attempt failed before completion. |
account.disconnected | Real-time | The account is in the disconnected state and cannot act until it is reconnected. |
account.paused | Real-time | Account synchronization was stopped or paused externally. |
account.error | Real-time | Account synchronization halted due to an error, action required. |
account.connecting | no_longer_realtime | An account connection flow is in progress (transient, high-frequency). |
account.permission_revoked | no_longer_realtime | Required LinkedIn permissions were withdrawn from the account. |
account.initial_sync.running | Real-time · opt-in | The initial history backfill started (informational, bracket open). |
account.initial_sync.completed | Real-time · opt-in | The initial history backfill completed, full LinkedIn history is now queryable. |
account.initial_sync.failed | Real-time · opt-in | The initial history backfill failed, the delivery carries a neutral reason. |
Subscribe to all or a subset when creating your webhook:
curl -X POST https://api.curviate.com/v1/webhooks \
-H "Authorization: Bearer cvt_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "account_status",
"request_url": "https://hooks.example.com/curviate",
"account_ids": ["acc_YOUR_ACCOUNT_ID"]
}'Omitting events subscribes to the 12 lifecycle events by default. The three
account.initial_sync.* events are opt-in. Name them explicitly in events[] to
receive them.
account.synced, account.creation_failed,
account.connecting, and account.permission_revoked carry
availability: "no_longer_realtime". They are not delivered
on the current platform. For account.creation_failed and
account.permission_revoked, detect the state via an on-demand account
read (GET /v1/accounts/{account_id}). For
account.synced and account.connecting there is no
equivalent event and no direct read-path signal. This is honest deprecation, not a
bug.
On each account connect, the initial history backfill is bracketed by a bounded
2-event sequence: exactly one account.initial_sync.running (bracket
open, informational), followed by exactly one of
account.initial_sync.completed or
account.initial_sync.failed. account.initial_sync.completed
is the actionable signal, the account's full LinkedIn history is now queryable.
These three events are opt-in.
account.connecting was historically a transient, high-frequency event
fired while a link was in progress. Today it carries
availability: "no_longer_realtime" and is not delivered. Do not build
on receiving it. Detect link-in-progress via the account read
(GET /v1/accounts/{account_id}) instead.
Connection lifecycle
account.created
Fired when a new account link completes successfully for the first time, the initial connect path. Use this event to start any post-onboarding logic in your agent.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.created",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "active",
"occurred_at": "2026-05-28T08:00:00.000Z"
},
"delivered_at": "2026-05-28T08:00:01.123Z"
}account.connected
Fired when the account is in a healthy, connected state. That covers a health check
confirming the account is fine, and a connect that turns out to reactivate an account
you had previously disconnected ("recovered": true) and finds it healthy.
A reactivating connect always reports the state the account actually comes back in, so
if the reconnection is not clean you get the event for that state instead, one of
account.reconnect_needed, account.restricted, or account.disconnected, matching
the status the connect response itself returned.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.connected",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "active",
"occurred_at": "2026-05-28T08:05:00.000Z"
},
"delivered_at": "2026-05-28T08:05:01.456Z"
}account.reconnected
Fired when an account that previously required re-authentication is back online, credentials refreshed in place without creating a new account ID.
Reactivating an account you had previously disconnected does not fire this event. That
connect reports the state the account comes back in, so subscribe to
account.connected (and the degraded states) if you want to hear about reactivations.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.reconnected",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "active",
"occurred_at": "2026-05-28T10:00:00.000Z"
},
"delivered_at": "2026-05-28T10:00:01.234Z"
}account.reconnect_needed
Fired when the account's session expires or its credentials become invalid. The
account remains in the workspace but is inactive until the user completes
re-authentication. Trigger a reconnect flow from the dashboard or call
POST /v1/auth/intent with the account's account_id in the body to
re-authenticate it in place.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.reconnect_needed",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "reconnect_needed",
"occurred_at": "2026-05-28T11:30:00.000Z"
},
"delivered_at": "2026-05-28T11:30:01.567Z"
}Sync states
account.synced · no_longer_realtime
Historically fired when a synchronization cycle completed successfully. It now carries
availability: "no_longer_realtime" and is not delivered on the current platform. there is no equivalent event and no read-path signal. Do not build on it. The shape it
carried is shown below for reference:
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.synced",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "active",
"occurred_at": "2026-05-28T08:10:00.000Z"
},
"delivered_at": "2026-05-28T08:10:01.789Z"
}Error and pause states
These are distinct states.
account.error means synchronization halted due to an error and action
is required to restore the account.
account.paused means synchronization was intentionally stopped or
paused, a deliberate state, not a failure condition. Your handler should treat
them differently: account.error warrants an alert or automated
reconnect attempt; account.paused is expected and requires no
immediate action.
account.error
Fired when account synchronization halted due to an error. Action is required to restore the account, check the dashboard for details and consider triggering a reconnect flow.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.error",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "reconnect_needed",
"occurred_at": "2026-05-28T12:00:00.000Z"
},
"delivered_at": "2026-05-28T12:00:01.890Z"
}account.paused
Fired when account synchronization was intentionally paused, a deliberate, non-error state. No immediate action is required.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.paused",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "reconnect_needed",
"occurred_at": "2026-05-28T13:00:00.000Z"
},
"delivered_at": "2026-05-28T13:00:01.012Z"
}account.restricted · not_realtime
LinkedIn restricted the account. Actions on it will fail until the restriction is lifted, so treat this as a stop signal: pause outreach for the account and surface it for a human to look at.
A daily account reconciliation is what normally notices the restriction, so allow up
to 24 hours for that path. A connect that observes the account already restricted can
also deliver this event immediately, so do not assume every delivery lagged a full
day. Either way, an on-demand read with GET /v1/accounts/{account_id} always
reports the current state.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.restricted",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "restricted",
"occurred_at": "2026-05-28T13:30:00.000Z"
},
"delivered_at": "2026-05-28T13:30:01.204Z"
}account.permission_revoked · no_longer_realtime
A LinkedIn-side scope or permission the account relies on was withdrawn; the account
may require re-authorization to restore access. This event carries
availability: "no_longer_realtime" and is not delivered on the current platform. detect the state via an on-demand account read (GET /v1/accounts/{account_id}). The
shape it carried:
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.permission_revoked",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "restricted",
"occurred_at": "2026-05-28T14:00:00.000Z"
},
"delivered_at": "2026-05-28T14:00:01.234Z"
}Terminal states
account.creation_failed · no_longer_realtime
The initial account-link attempt failed before completion, terminal, and no account
was established for operational use. This event carries
availability: "no_longer_realtime" and is not delivered on the current platform. detect the outcome via an on-demand account read (GET /v1/accounts/{account_id}). The
shape it carried:
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.creation_failed",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "disconnected",
"occurred_at": "2026-05-28T07:55:00.000Z"
},
"delivered_at": "2026-05-28T07:55:01.345Z"
}account.disconnected
Fired when the account is in the disconnected state and cannot act until it is
reconnected; data.status carries that state. Causes include an explicit disconnect,
removal via the dashboard or the DELETE /v1/accounts/{id} API, and a connect or
reconnect attempt that observes an already-disconnected account without removing it.
Where the account was actually removed, its account ID will subsequently 404 and any
webhooks scoped to it will stop delivering, but do not assume every delivery of this
event means the account is gone: read the account back
(GET /v1/accounts/{account_id}) if you need to tell the two cases apart.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.disconnected",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "disconnected",
"occurred_at": "2026-05-28T15:00:00.000Z"
},
"delivered_at": "2026-05-28T15:00:01.456Z"
}In-progress state
account.connecting · no_longer_realtime
Historically a transient, high-frequency event fired while an account connection flow
was in progress. It now carries availability: "no_longer_realtime" and is not
delivered on the current platform, and there is no equivalent event. Do not build on
receiving it. Detect link-in-progress via the account read
(GET /v1/accounts/{account_id}), and wait for account.created or account.connected
as the completion signal. The shape it carried:
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.connecting",
"data": {
"account_id": "acc_YOUR_ACCOUNT_ID",
"status": "connecting",
"occurred_at": "2026-05-28T07:59:30.000Z"
},
"delivered_at": "2026-05-28T07:59:31.678Z"
}Initial sync
The initial history backfill for a newly connected account is reported by three
opt-in events that form a bounded bracket, exactly one
account.initial_sync.running, followed by exactly one of
account.initial_sync.completed or account.initial_sync.failed. Name them in
events[] to receive them.
account.initial_sync.running
The initial history backfill has started. Informational, bracket open.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.initial_sync.running",
"data": {
"status": "active",
"account_id": "acc_YOUR_ACCOUNT_ID",
"event": "account.initial_sync.running",
"occurred_at": "2026-05-28T08:00:00.000Z"
},
"delivered_at": "2026-05-28T08:00:01.000Z"
}account.initial_sync.completed
The initial history backfill completed. This is the actionable signal: the account's full LinkedIn history is now queryable.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.initial_sync.completed",
"data": {
"status": "active",
"account_id": "acc_YOUR_ACCOUNT_ID",
"event": "account.initial_sync.completed",
"occurred_at": "2026-05-28T08:12:00.000Z"
},
"delivered_at": "2026-05-28T08:12:01.000Z"
}account.initial_sync.failed
The initial history backfill failed. The delivery carries a neutral reason whose
value is exactly one of internal, proxy, or provider.
{
"id": "wdl_YOUR_DELIVERY_ID",
"webhook_id": "wh_YOUR_WEBHOOK_ID",
"event": "account.initial_sync.failed",
"data": {
"status": "active",
"reason": "provider",
"account_id": "acc_YOUR_ACCOUNT_ID",
"event": "account.initial_sync.failed",
"occurred_at": "2026-05-28T08:12:00.000Z"
},
"delivered_at": "2026-05-28T08:12:01.000Z"
}On account.initial_sync.failed, reason is exactly one of
internal, proxy, or provider. Treat it as a
closed set; no other value is delivered.
Delivery payload
Every account-status data object carries account_id, event, occurred_at, and
status. account.initial_sync.failed adds reason. There is no timestamp field
on any Curviate delivery.
Lifecycle events (the 12):
{
"status": "active",
"account_id": "acc_YOUR_ACCOUNT_ID",
"event": "account.connected",
"occurred_at": "2026-05-28T11:30:00.000Z"
}account.initial_sync.* events (the 3):
{
"status": "active",
"reason": "provider",
"account_id": "acc_YOUR_ACCOUNT_ID",
"event": "account.initial_sync.failed",
"occurred_at": "2026-05-28T08:12:00.000Z"
}account_id: theacc_...ID of the affected account in your workspace.event: the canonical Curviate event name. Guaranteed on every delivery, and identical to the top-levelevent.occurred_at: when the state transition occurred. Guaranteed on every delivery.status: the account's lifecycle status once the event has been applied. One ofactive,connecting,reconnect_needed,restricted,disconnected. Guaranteed on every delivery, and the same value for a giveneventwhichever way Curviate noticed the change. It is the same vocabularyGET /v1/accounts/{account_id}returns in itsstatusfield.reason: only onaccount.initial_sync.failed; one ofinternal,proxy,provider.
The table below is the complete event to status mapping. Every delivery of an event
carries the status shown, so you can branch on either field.
event | data.status |
|---|---|
account.created | active |
account.connected | active |
account.reconnected | active |
account.synced | active |
account.connecting | connecting |
account.reconnect_needed | reconnect_needed |
account.error | reconnect_needed |
account.paused | reconnect_needed |
account.restricted | restricted |
account.permission_revoked | restricted |
account.disconnected | disconnected |
account.creation_failed | disconnected |
account.initial_sync.running | active |
account.initial_sync.completed | active |
account.initial_sync.failed | active |
The mapping is many-to-one, so data.status narrows the account state
but does not identify the event. account.restricted and
account.permission_revoked both report
"status": "restricted", and both are in the default subscription set,
so a default subscriber can receive either. The three
account.initial_sync.* events report "status": "active"
because the history backfill does not change the account's state. When you need to
know exactly what happened, branch on event; when you need to know the
resulting account state, read data.status.
Account status events carry structural state metadata only: no LinkedIn message content, profile text, or attachment data.
The data[] field-remapping array documented on other sources does not apply here.
Note that the API currently accepts a data array on an account_status create
and silently ignores it rather than rejecting it, so do not read a successful create
as confirmation that a remapping took effect.