What HeyLead keeps, for how long, and who sees it.

Last updated 24 September 2026

This page says what the code does today, with the file that does it. Nothing on it is a certification. HeyLead is not affiliated with LinkedIn. It reaches LinkedIn through Unipile, which is also not affiliated.

HeyLead is an AI agent for LinkedIn outreach: it finds the right people, writes to them in the voice of your own LinkedIn posts, follows up, and handles replies. It runs from Claude Code, Cursor, any MCP client or a web dashboard. Everything below applies to the hosted service at heylead.dev. A self-hosted install keeps its data on your machine; see Self-hosting.

What is stored

The workspace is the boundary. Every row below belongs to one workspace, in a Postgres database on Google Cloud SQL.

What is never stored

Your LinkedIn password. You connect LinkedIn on a page Unipile hosts. HeyLead mints the link, receives an account id back, and checks that id with Unipile before it binds the seat (app/services/hosted_auth_links.py). No table has a password column.

A website visitor's identity. The arrival counter (public/arrivals.js) sends the page path and the referrer's host name. No cookie, no local storage, no IP address, no browser fingerprint, no identifier. Moving between heylead.dev pages sends nothing. The privacy policy describes the same counter.

How a client signs in

An MCP client connects to https://heylead.dev/mcp with OAuth 2.1. It registers itself (/oauth/register, so no pre-shared id), opens the browser, you sign in with Google and choose a workspace on the consent page, and the client keeps a token. PKCE with S256 is required. The full flow is in Authentication.

WhatValueWhere the code says so
Scopesoutreach:read, outreach:writeapp/mcp/oauth_metadata.py
Access token1 hourapp/services/user_tokens.py
Refresh token90 daysapp/services/oauth_store.py
Authorization code60 secondsapp/services/oauth_store.py
RevokePOST /oauth/revokeapp/routers/oauth.py

A read-only connection (outreach:read) can show campaigns, contacts, replies and results but never send. outreach:write is what lets a client write and send as you, and launch or stop a campaign. An access token is bound to the MCP endpoint: it dies after an hour, is refused the moment it is revoked, and is not accepted by the dashboard's own API. A revoked refresh token stops the client at its next refresh.

Retention

Your campaigns, contacts and messages stay until you delete the workspace. The operational records around them expire on their own:

RecordKept forWhere the code says so
Finished job rows (the scheduler's queue)7 daysapp/services/global_retention.py
Action counters (per-day send tallies)2 daysapp/services/global_retention.py
Event log30 daysapp/services/global_retention.py
API request log30 daysapp/routers/scheduler.py
Aggregated, depersonalised message insights90 daysapp/services/insight_store.py

The three fleet-wide sweeps run every 6 hours and stamp the database when all three finish. A watchdog on a separate service reads that stamp and raises an alert when it is more than 24 hours old, so a sweep that stops is noticed rather than assumed.

Export and delete

Export. contacts(action='export') returns your contacts as a table, CSV or JSON; analytics(action='export') does the same for a campaign's results. Both are tools in the tool reference, so any connected client can run them.

Delete the workspace. Settings → Workspace → Danger zone. You type the workspace name back, and the dashboard calls DELETE /orgs/{org_id}. Only the owner can do it, and not to their last workspace (app/services/org_store.py).

What that call removes today: the workspace, its members and its open invitations, and it releases the LinkedIn seat from the workspace. It does not disconnect LinkedIn at Unipile; the seat's session stays there until you disconnect it. For the rest of your data and for your account, write to [email protected]: the privacy policy commits to removing personal data within 30 days of an account-deletion request.

Who processes data on our behalf

Only services the api code calls. The first four touch every workspace; the rest run only when you connect or turn on that feature.

ServicePurpose
UnipileThe LinkedIn connection. Every profile read, invitation, message and reply passes through it (app/services/unipile.py).
Google CloudHosting: Cloud Run for the api, Cloud SQL (Postgres) for the database, Secret Manager for the keys.
Google GeminiWrites messages and comments, classifies replies, generates your ICP, embeds your website for retrieval (app/services/llm.py, app/services/rag/embeddings.py).
OpenAIFallback for the same calls when Gemini is rate-limited or down (app/config.py).
Google sign-in and CalendarSign-in for every account. Calendar only if you connect it, to create the meeting event when a prospect agrees to a time (app/services/google_calendar.py).
StripePro billing, only if you upgrade (app/services/billing.py).
ResendTransactional email to your own address: welcome, account connected, first lead accepted, weekly digest (app/services/transactional_email.py, app/services/welcome_email.py).
SerperWeb and news search: the web signals for your watchlist, and a news lookup about a company when a client asks for one (app/services/hosted_signals.py, app/services/llm.py).
FirecrawlCrawls the website you give for your ICP when a crawl key is set; otherwise HeyLead fetches the pages itself (app/services/rag/crawler.py).
SlackOnly if you connect it: the items that need you land in your Slack (app/services/slack_installs.py).
Telegram, a webhookOnly if you add one as a notification channel in Settings (app/services/notifications.py).
IPinfoOnly if you install the visitor snippet on your own website: turns a visitor's IP into a company name (app/services/ip_resolver.py).
XOnly if you connect X, to publish posts there (app/services/x_service.py).
Hume AIText-to-speech for voice memos. Off by default: a campaign is text only unless you change it (app/routers/campaigns.py).

The website itself is served by Vercel with Cloudflare in front; like any web server they see your IP address and browser details when a page loads. Plain http:// requests are redirected to https://. The privacy policy names the same providers.

Reporting a vulnerability

Write to [email protected]. The same address is published in /.well-known/security.txt. Say what you found and how to reproduce it; you will get a reply from a person.

What HeyLead does not claim

HeyLead is not affiliated with LinkedIn. It reaches LinkedIn through Unipile, which is also not affiliated.

Start free, no card

Related: Privacy · Terms · Authentication · Pricing · Self-hosting