Integrations / Migration
Migrating from Lemlist
Bring your Lemlist lead identifiers across when you migrate outreach to Crispy. Then use a webhook to push declined invitations back to a Lemlist email-only sequence so you don't lose long-tail prospects.
Why this matters
Migrations break things when identifiers don't survive the move. Keeping the Lemlist lead ID on the Crispy contact means analytics, dedup, and re-targeting all keep working across both systems during the transition window.
Step 1: Import with custom_attrs
Export your Lemlist leads to CSV, then import to Crispy with the Lemlist ID and email mapped into custom_attrs. Use any column prefix matching custom_attrs.<key>; the import_contacts tool maps these directly.
email,first_name,last_name,linkedin_url,custom_attrs.lemlist_lead_id,custom_attrs.lemlist_email,custom_attrs.industry
[email protected],Alex,Rivera,https://www.linkedin.com/in/alex-rivera,lem-9f8a7b6c,[email protected],SaaS
[email protected],Sam,Kim,https://www.linkedin.com/in/sam-kim,lem-1a2b3c4d,[email protected],FinTechThe same data flows through every webhook payload, so downstream automations always have access to payload.data.contact.custom_attrs.lemlist_lead_id.
Step 2: Subscribe to invitation.declined
Decline-on-LinkedIn is a strong signal that the connection-request channel is dead for that prospect, but email might still work. Send them back to Lemlist for email-only follow-up.
curl -X POST https://crispy.sh/api/v1/subscriptions \
-H "Authorization: Bearer crispy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"event": "invitation.declined",
"target_url": "https://hook.us2.make.com/your-make-webhook-id"
}'Step 3: Wire to Lemlist's API
- In Make or n8n, create a scenario with a webhook trigger pointing at Crispy.
- Verify the signature using the HMAC verification snippet.
- Add an HTTP → Make a request module:
PATCH https://api.lemlist.com/api/leads/{{ payload.data.contact.custom_attrs.lemlist_lead_id }} Authorization: Basic <base64 of api-key:> Content-Type: application/json { "campaignId": "your-email-only-campaign-id", "email": "{{ payload.data.contact.custom_attrs.lemlist_email }}" } - If a Lemlist lead ID is missing (newer Crispy contact never imported from Lemlist), short-circuit gracefully: filter the route on
payload.data.contact.custom_attrs.lemlist_lead_idbeing non-empty before the Lemlist call.
Sample payload
The Lemlist identifiers you imported are surfaced under contact.custom_attrs:
{
"payload_version": "1",
"id": "00000000-0000-0000-0000-000000000903",
"event": "invitation.declined",
"timestamp": "2026-04-29T18:00:00.000Z",
"webhook_id": "00000000-0000-0000-0000-0000000009h4",
"account_id": "00000000-0000-0000-0000-0000000009a1",
"data": {
"contact": {
"id": "00000000-0000-0000-0000-0000000009c3",
"workspace_id": "00000000-0000-0000-0000-0000000009w1",
"connection_status": "declined",
"current_campaign_id": "00000000-0000-0000-0000-0000000009p1",
"tags": ["q4-target"],
"custom_attrs": {
"industry": "SaaS",
"lemlist_lead_id": "lem-9f8a7b6c",
"lemlist_email": "[email protected]"
},
"external_ids": {}
},
"source_event": "invitation.declined",
"triggered_at": "2026-04-29T18:00:00.000Z"
}
}Troubleshooting
- custom_attrs are missing from the payload.
- Check the import. CSVs that use a header like
lemlist_lead_idinstead ofcustom_attrs.lemlist_lead_idwon't map. Re-export with the prefix and re-import the affected contacts. - Lemlist returns 404 on the lead ID.
- The lead may have been removed from Lemlist after export. Add a fallback path that creates the lead by email instead of patching by ID.