Lemlist to Attio Sync
Attio is the CRM. Lemlist is the outbound engine. This custom Lemlist-to-Attio app is the bidirectional bridge between them, so reps can enroll Attio People into Lemlist campaigns and Lemlist engagement events land back on the matched Attio Person record.
Who this is for
This build is for outbound sales teams that run multi-channel sequences in Lemlist and use Attio as the CRM of record. It fits when reps work from Attio, but the actual email, LinkedIn, WhatsApp, Aircall, API, and enrichment activity happens in Lemlist.
It is also a fit when engagement data needs to become CRM state. Lemlist can send the activity stream, while Attio needs the current answer: who is interested, who is paused, who unsubscribed, and which Person record should own the activity history.
How the sync works
The sync has four practical pieces: outbound enrollment from Attio, inbound event processing from Lemlist webhooks, Person matching, and paired writes to an events stream plus cumulative Person state.
Outbound enrollment flow
A sales rep selects a Person in Attio, clicks Add to Lemlist campaign, picks a campaign, and chooses the scoped enrichment options. The app calls Lemlist's create-lead endpoint, POST /api/campaigns/{campaignId}/leads/, with the Person fields needed for the campaign.
Lemlist documents four boolean enrichment query parameters for this endpoint: findEmail, verifyEmail, findPhone, and linkedinEnrichment. Lemlist also documents deduplicate as a boolean query parameter that searches the email address in other campaigns and does not insert the lead if the email already exists there. The icebreaker value is a string body field for the icebreaker text, not a boolean enrichment toggle.
If Lemlist rejects a second enrollment for a lead it considers a duplicate, the app surfaces that result back to the rep instead of silently swallowing it. The public Lemlist docs reviewed do not document a duplicate-specific status code, so this page does not name one.
Inbound event flow
Lemlist sends a webhook POST to the app for each subscribed event type. The app checks the optional shared secret value that Lemlist echoes in the JSON body, deduplicates the event against an idempotency store, resolves the lead to an Attio Person, writes one entry to the Lemlist events list, and updates cumulative Lemlist state attributes on the Person.
The primary idempotency key is the Lemlist activity _id. If that field is missing, the fallback key uses type + campaignId + leadId + createdAt. The standard idempotency window is 24 hours, which covers repeated deliveries without turning the event list into a permanent dedupe database.
Person matching
Matching is email-first. When the Lemlist payload carries an email address, the app queries Attio People through the email_addresses filter and links the event to the matching Person.
If email is missing, the app tries LinkedIn URL second. If neither identifier finds a Person, auto-create is the default: the app creates a new Attio Person from the identifiers Lemlist provided and links the event there. Teams that want strict matching can scope auto-create off.
State vs. stream
The events list is the historical stream. It answers what happened, when it happened, which campaign produced it, and which Lemlist lead or activity ID it came from.
The Person attributes are cumulative state. They answer what is true now, such as current interest status, paused state, unsubscribed timestamp, last paused time, last resumed time, and last synced time. The app writes the event entry and the Person state together so list views, workflow triggers, and Person records stay consistent.
What we configure during scope
The scoping call turns the Lemlist-to-Attio pattern into your build. These decisions are made before the app is installed.
| Decision | What we decide with you |
|---|---|
| Lemlist coverage | Which event categories should be on, which campaigns matter, and whether any campaign-specific subscription rules are needed. |
| Enrichment toggle defaults | Default values for findEmail, verifyEmail, findPhone, linkedinEnrichment, and deduplicate on outbound enrollment. |
| Attio destination | Which Attio People object receives the integration, and how the Lemlist events list should be named for your workspace. |
| Custom attribute mappings | Any fields beyond the standard Lemlist payload that should be written to Attio for reporting, routing, or workflow triggers. |
| Auto-create People behavior | Auto-create is on by default when no Attio Person matches. It can be scoped off when the CRM should only accept events for existing People. |
| Person matching strategy | Email-first, LinkedIn-second matching, plus any workspace-specific identifier overrides that should run before creating a new Person. |
| Reliability | The idempotency window, replay and reconciliation behavior, and what operators should be able to rerun from Attio after missed or delayed events. |
Why this works with the Lemlist API
Lemlist exposes the API surface this pattern needs: API-key authentication, webhook subscription management, documented webhook objects and event types, lead enrollment into campaigns, and published rate limits. Authentication uses HTTP Basic auth with an empty username and the API key as the password, sent in the Authorization header (Lemlist authentication docs).
Webhook management is documented through Add Webhook, Get Many Webhooks, Delete Webhook, and the Webhook object. A webhook can set targetUrl, an optional type, an optional campaignId, and an optional secret. Omitting type sends all events, but this app uses explicit subscriptions so the Attio workspace receives only the categories chosen during scope.
The lead enrollment side uses Create Lead in Campaign. The documented endpoint is POST /campaigns/{campaignId}/leads/ under Lemlist's API base path. The documented enrollment options include deduplicate, findEmail, verifyEmail, findPhone, and linkedinEnrichment as query parameters, with icebreaker as a body string field.
Lemlist documents webhook categories and event names such as contacted, hooked, attracted, warmed, interested, notInterested, emailsBounced, emailsUnsubscribed, campaignComplete, paused, resumed, annotated, linkedinInviteDone, linkedinVoiceNoteDone, linkedinVoiceNoteFailed, whatsappMessageSent, whatsappMessageDelivered, whatsappMessageOpened, whatsappReplied, aircallCreated, aircallEnded, aircallDone, enrichmentDone, enrichmentError, apiDone, and apiFailed. The interested category groups Lemlist's interested-style events across channels such as email, LinkedIn, Aircall, API, and manual updates, but Lemlist does not document an exact expansion contract. The same caution applies to notInterested. Lemlist's published rate limit is 20 requests per 2 seconds per API key across all routes, and the version page says v1 is deprecated and v2 should be used.
How reliability is handled
Lemlist does not document HMAC webhook signing in the public docs reviewed. It documents an optional shared secret that is encrypted at rest, not returned by GET /hooks, immutable after creation, and echoed in the webhook JSON body. The app verifies that body value when a secret is configured.
Lemlist also does not document webhook retry, ordering, or delivery guarantees in the public webhook pages reviewed. The integration therefore treats webhook events as at-most-once on arrival, processes defensively, and exposes a reconciliation or resync path instead of depending on Lemlist replay.
Idempotency uses a 24-hour TTL on the activity _id. When _id is missing, the fallback key is type + campaignId + leadId + createdAt. This prevents duplicate Attio entries from repeated deliveries while still keeping the event stream readable and bounded.
How setup works
Setup separates API access, Attio provisioning, and webhook activation. Outbound enrollment can work as soon as the API key is connected, while inbound events stay off until you choose the subscription set.
- Scope the integration. We confirm which Lemlist event categories should be active, which campaigns matter, the default enrichment settings for outbound enrollment, and any custom attribute mappings beyond the standard Lemlist payload.
- Install the Attio app and connect Lemlist. We install the custom Attio app into your workspace. In Workspace Settings, you paste your Lemlist API key. Lemlist documents HTTP Basic auth with an empty username and the API key as the password, sent in the Authorization header.
- Provisioning runs automatically. After the API key is connected, the app provisions the Lemlist events list on People and the cumulative Lemlist state attributes on the Person record. No webhooks fire yet.
- Pick webhook subscriptions. In Workspace Settings → Webhook Subscriptions, you choose the event categories to enable, then click "Save & Sync Lemlist". The app creates enabled subscriptions with POST /hooks and deletes disabled subscriptions. If your Lemlist plan tier gates the webhook API, the integration surfaces the plan-required response from Lemlist so you know which feature your plan does not include.
- Use outbound enrollment. The per-Person and bulk "Add to Lemlist campaign" record actions work as soon as the Lemlist API key is connected. They do not depend on inbound webhook subscriptions being active.
Known limitations
Custom install, not a Marketplace app
This is a scoped custom engagement. It is not a public Lemlist Marketplace app, not an Attio Marketplace app, and not a one-click install.
Webhook subscription can be plan-gated by Lemlist
Lemlist's public developer docs reviewed do not name a required plan tier for webhook API management. If your Lemlist workspace receives a plan-required response when the app creates or deletes webhooks, the integration surfaces that response so you know which feature Lemlist is gating.
All events are off by default at install
The app provisions Attio surfaces before it subscribes to Lemlist events. You choose the event categories in Workspace Settings and then sync them to Lemlist, which prevents a workspace from receiving unwanted campaign activity on day one.
Auto-create People is the default behavior
When no Person matches by email or LinkedIn URL, the app creates a new Attio Person by default. Teams that require strict CRM ownership can scope this off so unmatched webhook events are skipped or queued for review.
Bulk enrollment respects Attio's 250-record cap
The bulk record action follows Attio's 250-record per-action cap. Larger enrollment runs need a scoped batch job or another controlled import path.
Lemlist webhook authenticity uses a shared secret in the body
Lemlist does not document HMAC signing for these webhooks. The documented authenticity mechanism is the optional secret echoed in the JSON body, so reliability is built around body-secret verification, idempotent writes, and reconciliation.
Removed or undocumented event names are not active categories
Lemlist lists skipped as removed, so it is not surfaced as an active category. Event names not found in the reviewed public enum, such as sequenceStarted, sequencePaused, sequenceCompleted, leadAdded, leadUpdated, and leadUnsubscribed, are not treated as active categories unless Lemlist surfaces them for your workspace and they are verified during scope.
One Lemlist account, one Attio workspace by default
The standard pattern connects one Lemlist account to one Attio workspace. Multi-account routing, multiple Lemlist teams, or multiple Attio workspaces need explicit scope.
Frequently asked questions
Is this a Marketplace app or a custom engagement?
This is a client-proven Lemlist-to-Attio custom app pattern, not a public Marketplace app or a one-click install. We install it into your Attio workspace as part of a scoped engagement and adapt the campaign coverage, event categories, matching behavior, and field map to your workspace.
What direction does data flow?
Data flows both ways. Attio sends selected People into Lemlist campaigns through record actions and bulk record actions. Lemlist sends engagement events back into Attio through webhook subscriptions, where the app writes one event entry and updates the matched Person record.
How are Lemlist leads matched to Attio People?
The matching order is email first, LinkedIn URL second, and auto-create third. Email matching uses Attio People email addresses when Lemlist sends leadEmail or an equivalent lead email field. If email is missing, the app checks the LinkedIn URL. Auto-create is on by default and can be scoped off.
What does "Add to Lemlist campaign" do?
The action lets a rep choose a Lemlist campaign and enrollment options, then calls Lemlist lead creation for each selected Person. The documented endpoint is POST /campaigns/{campaignId}/leads/ under Lemlist API v2. The official deduplicate query parameter checks email in other campaigns; it is not an email plus LinkedIn dedupe rule inside the target campaign.
How fast do Lemlist events show up in Attio?
Lemlist describes webhooks as real-time POST callbacks, but the public docs reviewed do not document retry, ordering, or delivery guarantees. The integration processes each event as it arrives, deduplicates it, writes the event entry, and keeps a reconciliation path for recovery.
Why does the integration surface engagement signals as categories instead of raw event names?
Lemlist documents category-style events such as contacted, hooked, attracted, warmed, interested, and notInterested. We use those categories for Attio filtering and reporting while preserving raw Lemlist payload details where they are useful. For interested and notInterested, the page does not claim an exact expansion list because Lemlist does not document the exact grouping contract.
Does the integration update the Person record itself, or only an events list?
It updates both. The events list is the historical stream of Lemlist activity. The Person record holds cumulative state such as current interest status, paused state, unsubscribed timestamp, and last synced timestamp. Both writes happen together so reporting does not drift between the stream and the Person state.
Do Lemlist plan tiers affect what the integration can do?
They can. If Lemlist gates a webhook-management feature for your workspace, the integration surfaces the plan-required response from Lemlist so you know which feature your plan does not include. We do not name a specific required tier unless Lemlist confirms it for your account.
API sources checked
- Lemlist API Overview
- Lemlist Authentication
- Lemlist Rate Limits
- Lemlist API Errors
- Lemlist Versions
- Lemlist Add Webhook
- Lemlist Delete Webhook
- Lemlist Get Many Webhooks
- Lemlist Webhook object
- Lemlist Create Lead in Campaign
- Lemlist Lead object
- Lemlist Add Custom Variables on Leads
- Lemlist Update Lead in a Campaign
- Lemlist Get Many Activities
- Lemlist Activity object
- Lemlist Get User Channels
- Lemlist Channel object
- Lemlist documentation index
- Attio App SDK record actions
- Attio App SDK bulk record actions