Two inboxes, synced separately
The regular messenger and the Sales Navigator inbox are separate surfaces with their own threads and their own clocks. Every thread carries amessenger_type.
SN threads sync only once the platform detects the subscription (
premium_check, its own
surface, every 360 minutes), which has to run before the Sales Nav Conversations row
appears. A basic-messenger verb on an SN thread answers 422 messenger_type_mismatch.
What the initial backfill imports
Onboarding walks a fixed ordered set of surfaces (connections, connection requests, connection invitations, the regular inbox, the premium check, the Sales Navigator inbox, then the profile snapshot) and the account unlocks when it finishes. Every thread is imported, since the run pages to an empty page. Not every message is. During onboarding theself_account_sync daily budget is raised from 200 to 1000 calls per
day; the first profile snapshot taken after onboarding completes recomputes it back down to
200. A first-time backfill runs to completion whatever hours are configured; only recurring
syncs wait for the window: an inbox sync spends that budget and nothing else.
How often each surface re-syncs
Three things change the effective interval: your per-account setting on the Account Sync
tab, the sync window (a run due outside the configured
hours parks with
schedule:closed until it reopens), and the free plan floor.
A recurring run is not a full re-read: it walks the directory newest first and stops once a page
holds 10 threads it already knows, and regular-inbox pages are capped at 25 threads by
the browser plugin (the Sales Navigator sweep pages at 100). Only threads whose event count or
last activity moved ahead of the stored copy are read.
Realtime push, and what it does not do
While the account’s browser runs, the plugin listens to LinkedIn’s own realtime stream and tells the platform when a message arrives: end to end, a received message reaches thelinkedin-messages.received webhook in about 8 seconds, against the 180 minute sweep.
The push carries identifiers only and never writes a message row. For a thread the platform
already knows it schedules a refresh of that one thread, read through the account’s own
browser, and the rows land tagged source='push'. For a thread it has never seen it falls back
to an account-wide conversations refresh, whose rows are tagged source='sync' like the
sweep’s: the webhook still fires, but the live Inbox screen does not update until the sweep.
Signals it drops:
The rest are environmental:
disabled (off platform-wide), unhandled_type (typing indicator
or read receipt), no_linkedin_account, account_not_found and dispatch_failed.
A dropped signal is never a lost message: the worst case is that it appears on the next sweep
instead of within seconds. Your own reply typed in the LinkedIn UI is always in that class, and
lands on the sweep as type='outbox' with automation='synced': discovered, not sent.
Force a refresh now
A head refresh reads the newest page of one thread or account synchronously and returns the rows themselves; use it in almost every case. A full sync reconciles the head of the directory in the background and returns a sync run to poll; like every recurring run it stops once a page holds 10 threads it already knows, so it will not reach an old thread that is missing locally. Only a sync reset re-walks the directory end to end.curl
refresh block with counters and a stop_reason of overlap,
covered, page_cap or exhausted. A head refresh never stamps the account’s sync clock and
never emits linkedin-conversations.sync-completed, so it does not postpone the next drain.
Every head refresh page and every background message pull spends one call from the account’s
self_account_sync budget: 200 calls per day, with a 6 second pacing delay and bursts
of 5. When it is spent, reads answer 429 rate_limited with limit_type: "self_account_sync" and a retry_after, and parked runs resume at the next daily reset or
when the limit is raised. See smart limits and warmup.Why a message may not appear yet
Three timestamps answer most of it:last_activity_at (newest activity LinkedIn reports),
last_stored_message_at (newest message stored) and last_message_sync_at (last pull). Activity
newer than stored means the thread is already flagged and the next run drains it. Otherwise:
- The account is still onboarding. The drawer shows Initial sync in progress and head
refreshes answer
429 rate_limitedwithcontext.reason: "initial_sync_pending". - A sync is already running for that surface. A second one does not queue: it answers
429 rate_limitedwithreason: "sync_in_progress"and the workingsync_run_sidto poll. - The sync budget is spent or held. The run shows a
wait_reasonstarting withlimit:(limit:daily_saturation,limit:held,limit:linkedin_quota_hit,limit:delay_not_elapsed). Raise the Self account sync limit on Smart Limits with reset hold to resume them all now. - The browser is down or logged out. Sync verbs still enqueue and hand back a
sync_run_sid; the run then parks with abrowser:wait_reasonand retries roughly 30 minutes out, while a head refresh answers503 service_unavailable. Budget parks wake on a raised limit, browser parks wait for their retry. - It is outside the sync window. The run carries
wait_reason: "schedule:closed"and anext_attempt_atset to the next opening. Widen the window or head-refresh now. - The message is older than the newest 50 in its thread. No sweep backfills older history:
call
get-my-lateston that thread to pull the newest page of up to 100 messages. - It was a push drop and the sweep has not run. Your own reply typed on LinkedIn, a duplicate signal, a burst above 10 refreshes a minute and free plan accounts all skip the fast path: the wait is then up to 180 minutes (regular), 120 (Sales Navigator), 1440 (free plan).
- Still nothing? Send support the account sid (
ln_ac_…), the Account Sync row with its status and next due time, and the sync run sid (ln_sy_…) with itswait_reason.
pending, then in_progress, then completed
or cancelled. One stuck at in_progress is almost always waiting, so read its wait_reason
and next_attempt_at.
Reading the stored copy
A message carriestype (inbox or outbox), automation (auto, manual, connect or
synced) and linkedin_type (message, inmail or connection_note). Messages are
append-only on both surfaces: no edit, no delete, no unsend, no recall. There are no read
receipts and no open tracking, and marking a thread read changes the state on LinkedIn only.
There is no export button and no CSV. linkedin-messages/search and
linkedin-conversations/search page by cursor and touch neither the browser nor the budget
(filters and sorts are in the API reference), and a conversation
from another workspace answers 403 wrong_team, not 404. Webhooks deliver
linkedin-messages.received, .sent and .send-failed, plus linkedin-conversations.created
and .sync-completed.
A send that fails never creates a message row: the failure lands on the activity log and is
emitted as
linkedin-messages.send-failed carrying the linkedin_account_sid, the
linkedin_conversation_sid and an error_code (the activity-log sid is not in the payload;
find the row by account and time). If a send times out and you are unsure whether it went
through, search messages on that conversation with type: "outbox" and sent_at after the
attempt. A blind retry can double-send.