If you suspect a missed LinkedIn accept

If you accepted a prospect on LinkedIn but the WarmySender dashboard still shows them as Invited, this guide explains how accept-detection works behind the scenes and how to bring the dashboard back in line. Most of the time the dashboard catches up on its own within seconds. When it doesn't, three short steps will fix it.

Note: a separate situation that can look like 'missed accepts' is starting/resuming a campaign that has zero enrolled prospects with a usable LinkedIn URL — the dashboard will simply sit at zero with no progress. As of May 2 2026 (LL#290), Resume/Start now blocks that with a soft amber Alert and an 'Import contacts' CTA so you know the campaign needs prospects with LinkedIn URLs before it can send anything. See the dedicated guide 'Why can't I resume my campaign? (No prospects with LinkedIn URL)' for details.

The five acceptance detection paths
When a prospect accepts your invite on LinkedIn, our system can learn about it through any of five different signal paths — and as of May 2 2026 (LL#289) all five fire a Slack/n8n/Zapier notification consistently:

1. Webhook bridge — the canonical path. Unipile pushes a `users.relations.new_relation` webhook the moment LinkedIn marks the invite accepted; we resolve the prospect and stamp the dashboard within ~1 second.
2. Webhook direct CP match — when the webhook member ID matches an enrolled prospect directly via `linkedin_member_id`, even if the bridge resolver couldn't attribute it to a specific campaign.
3. Webhook Sales Nav decode — when your prospect was imported from a Sales Navigator URL (`linkedin.com/sales/lead/...`), we decode the lead ID to compare against the webhook's member ID.
4. Unified polling — every 30 minutes we ask Unipile for a fresh view of your relations list and reconcile against any enrolled prospects that don't yet have a `linkedin_accepted_at` timestamp. This catches webhooks that dropped or arrived late.
5. Engine already-connected — when a campaign tries to send_invite to a prospect who is already a 1st-degree connection, Unipile returns a 422 `already_connected` skip; we treat that as a forensic accept signal and stamp the timestamp.

All five paths are idempotent and produce a single Slack notification per real acceptance via the dedupe key `li-accept:<linkedinAccountId>:<enrollmentId>` in the webhook outbox.

Slack notifications for acceptances
If you've configured a Slack webhook (Settings → Integrations), every detected acceptance fires a `linkedin.invite_accepted` event to your webhook outbox — once per prospect per accept, idempotent under Unipile retries. As of May 2 2026 (LL#289), all five of our acceptance source paths emit Slack consistently: the Unipile webhook bridge, direct campaign-prospect matches, Sales Navigator URL decode, the 30-minute polling fallback, and the engine's already-connected detection during a send_invite skip. Before the fix, only the bridge path emitted; the other four stamped the accept on the dashboard but the Slack outbox was silent. If you saw fewer Slack messages than acceptances on your LinkedIn timeline before May 2, that's why — it's now resolved. The dedupe key (`li-accept:<linkedinAccountId>:<enrollmentId>`) collapses duplicate fires when both the webhook and the polling fallback detect the same accept.

How accept-detection works
WarmySender watches for accepts using three signals, in priority order. The first one to land wins, and the others quietly become no-ops thanks to idempotent stamping. You never get double-counted.

Signal 1 — Unipile webhook (fastest, sub-second)
Our LinkedIn integration partner Unipile pushes a `new_relation` event the moment LinkedIn marks the invite as accepted. The event arrives at our server in well under a second on a healthy day. We match the event to your prospect, stamp the accept timestamp, and flip the status to Connected — all in a single atomic database write. The next campaign step (a follow-up message, for example) is queued for the next valid sending slot in your campaign window. This is the path most accepts take. Source: Unipile developer docs at developer.unipile.com.

Signal 2 — account-status polling (every 30 minutes)
If the webhook drops or arrives late, polling catches it. Every 30 minutes we ask Unipile for a fresh view of your LinkedIn relations and reconcile the list against our records. Anything new gets stamped as accepted, with the timestamp set to the actual accept time. The polling cadence is throttled per Unipile's documented per-account spacing — we never burst, never call faster than safe limits allow, even when there's a backlog.

Signal 3 — message-received inferred (when the prospect replies)
If a prospect replies before either of the first two signals lands, we infer the accept from the reply itself — you can't reply to a non-connection on LinkedIn, so a reply is forensic proof of acceptance. We stamp both timestamps in one write: linkedin_accepted_at and linkedin_replied_at. The dashboard counts both correctly. This is the rare belt-and-suspenders path, but it exists so a fast reply never silently swallows the upstream accept.

What you'll see on the dashboard
The dashboard's accept counter is computed live from your prospect rows — specifically, the count of prospects in the campaign whose linkedin_accepted_at column is not empty. There is no second cache to fall out of sync. If a prospect shows as Connected, that means their accept timestamp is set. If they show as Invited, the timestamp is still empty.

'Connected' means the prospect accepted your invite and the system has recorded it. From this point forward, follow-up steps that depend on the accept (such as a wait_accept condition or a send_message step) become eligible to fire on their next scheduled time, respecting your campaign's sending window and your LinkedIn account's per-day safety caps. The next campaign step does NOT fire instantly on accept — it queues for the next valid send time in your campaign's schedule plus a small random jitter so the action looks human. This is by design: account safety wins over speed.

If you suspect a missed accept (3 steps, in order)
1) Wait 30 minutes. If the webhook was dropped or delayed, the polling fallback will catch up on its next pass. Hard-refresh the dashboard after the half-hour mark — the prospect should now show as Connected.
2) Click 'Resync prospect' on the prospect detail page. If you don't want to wait 30 minutes (or you've already waited and the dashboard still shows Invited), open the prospect's detail view and click the Resync button. We re-poll Unipile for that one prospect's relation status and stamp the accept if it's there. Multiple clicks within the per-account spacing window collapse into a single call.
3) Contact support. If the prospect is still showing as Invited after a Resync click, email [email protected] with the prospect URL and the campaign name. We can manually re-drive the accept stamp from our end and confirm whether the issue is platform-side (something we should fix) or LinkedIn-side (something to investigate with Unipile).

Account safety
We never make extra Unipile calls just to verify state. Every shortcut you can imagine — 'just poll on demand,' 'just re-fetch the relations list every minute,' 'just confirm before stamping' — risks tripping LinkedIn's automation detection and getting your account restricted. A banned LinkedIn account is unrecoverable. So our default is the opposite: poll at the documented per-account spacing, lean on webhooks for fast updates, and accept that some accepts arrive a few minutes late rather than risk an account flag.

The polling fallback is throttled to Unipile's documented per-account spacing (see developer.unipile.com for the canonical reference). We do not exceed that pace, even when there is a backlog of prospects waiting on accept signals. If your dashboard takes 30 minutes longer than usual to reflect an accept, that's the safety envelope holding — not a bug.

Examples
Example 1 — the webhook path (most common):
'I accepted Vishwanath's invite on my phone at 3:14 PM. The dashboard updated 8 seconds later.' That's signal 1: Unipile pushed the new_relation event the moment LinkedIn fired it, our handler matched it to Vishwanath's enrollment, and the row was stamped Connected before you put your phone down. The next follow-up step is queued for your campaign's next valid sending slot.

Example 2 — the polling fallback (rare):
'I accepted Selva's invite yesterday afternoon. I checked the dashboard this morning and she's still showing as Invited.' That's a missed webhook. Don't panic: click 'Resync prospect' on Selva's detail page. The Resync fetches the latest relation status directly from Unipile within the per-account spacing window, finds the accept, and stamps it. Selva flips to Connected within a few seconds. The follow-up step is queued for the next valid sending slot. No prospects are lost.

References: LL#282 (markProspectAccepted atomic stamp + status flip under the post-invite-status CHECK constraint, May 1 2026). PRD: PRD-2026-05-01-LINKEDIN-ACCEPT-STAMPING-REGRESSION.md.

Back to all documentation | Contact support