n8n Integration (Self-Hosted)
Connect WarmySender to n8n, the open-source workflow automation tool, to fan out events (email replies, LinkedIn replies, invite acceptances, bounces) into any downstream system — CRM, databases, Slack, Teams, Discord, Sheets, custom APIs.
Why n8n:
- Self-hosted — your data stays on your infrastructure.
- No per-task pricing — unlimited executions.
- 400+ integrations with a visual workflow builder.
- Ideal for teams with privacy requirements or high automation volume.
Setup:
Step 1: Create a Webhook trigger in n8n
• Open your n8n instance and create a new workflow.
• Add a 'Webhook' node as the trigger (under Core Nodes).
• Set HTTP Method to POST.
• Set Response Mode to 'Immediately' (we need a 200 within 5 seconds — our timeout).
• Activate the workflow and copy the Production URL (NOT the Test URL — test URLs expire).
Step 2: Register in WarmySender
• Settings → Webhooks → New webhook.
• Paste the n8n Production URL.
• Subscribe to desired events (see list below).
• Save and click the test button to confirm delivery.
Step 3: Route by event type
Add a Switch node after the Webhook trigger, routing on {{ $json.type }}:
• reply.received — Email campaign reply
• linkedin.reply_received — LinkedIn message / InMail reply (includes reply body in messageText)
• linkedin.invite_accepted — LinkedIn connection request accepted
• email.bounced — Hard bounce
• email.unsubscribed — Unsubscribe link clicked
• email.opened / email.clicked — Open / link click (high volume)
• prospect.suppressed — Prospect added to suppression list
• limit.hit — Daily sending limit reached
• webhook.test — Test event fired from Settings
Step 4: Add downstream actions
Example chains (from real customer setups):
1) LinkedIn reply → Slack + HubSpot task:
Webhook → IF (type = linkedin.reply_received) → Slack node (post to #sales-replies with {{ $json.data.messageText }}) → HubSpot node (Create Task: 'Follow up on LinkedIn reply').
2) Every reply → Google Sheets log:
Webhook → IF (type contains 'reply') → Google Sheets 'Append Row' (Timestamp, Channel, Prospect, Campaign, Body).
3) Invite accepted → Pipedrive deal:
Webhook → IF (type = linkedin.invite_accepted) → Pipedrive node (Create Deal in 'Connected' stage, mapped from {{ $json.data.prospectLinkedinUrl }}).
4) Per-campaign routing to different Slack channels:
Webhook → Switch (by {{ $json.data.campaignId }}) → multiple Slack outputs, one per channel.
5) Bounce log + ops alert:
Webhook → IF (type = email.bounced) → Postgres Insert → Microsoft Teams node.
Signature Verification in n8n (publicly-reachable endpoints):
Every payload is signed with HMAC-SHA256. The X-Warmy-Signature header is Stripe-style `t=<unix_ms>,v1=<hmac_hex>` — you must parse it and verify against the RAW request body (not the parsed JSON).
Two setup steps:
- On the Webhook trigger node, under 'Options', enable 'Raw Body' so the raw bytes are preserved on `$binary.data`.
- Add a Function node before the Switch with this code:
const crypto = require('crypto');
const secret = $env.WARMY_WEBHOOK_SECRET; // set in n8n credentials/env — never hardcode
const headers = $input.first().headers;
const sigHeader = headers['x-warmy-signature'] || '';
const parts = Object.fromEntries(sigHeader.split(',').map(p => p.split('=')));
const ts = parts.t; const sig = parts.v1;
if (!ts || !sig) throw new Error('Missing signature');
if (Math.abs(Date.now() - parseInt(ts, 10)) > 5 * 60 * 1000) throw new Error('Stale timestamp');
const rawBody = Buffer.from($input.first().binary.data.data, 'base64').toString('utf8');
const expected = crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
const a = Buffer.from(sig, 'hex'); const b = Buffer.from(expected, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) throw new Error('Bad signature');
// re-parse body for downstream nodes
$input.first().json = JSON.parse(rawBody);
return $input.all();
If Raw Body isn't enabled, re-serializing $json will NOT byte-match what we signed (key order / whitespace / unicode escaping all differ) and verification will fail 100% of the time.
LinkedIn Event Payload Shape:
linkedin.reply_received:
data: { prospectId, prospectName, prospectLinkedinUrl, campaignId, enrollmentId, linkedinAccountId, messageText, threadId, receivedAt }
linkedin.invite_accepted:
data: { prospectId, prospectName, prospectLinkedinUrl, campaignId, enrollmentId, linkedinAccountId, acceptedAt }
Note: linkedin.reply_received fires once per enrollment on the FIRST reply. Subsequent messages in the same thread don't re-fire. If you need every inbound message, contact support — this isn't currently exposed as a standalone event.
Advanced: WarmySender API from n8n:
Beyond webhook consumption, use n8n HTTP Request nodes to call the WarmySender REST API:
• Create prospects: POST /api/v1/prospects (email, firstName, lastName, company, linkedinUrl, custom fields).
• Enroll in campaign: POST /api/v1/campaigns/:id/enrollments with prospectIds.
• Unenroll: DELETE /api/v1/campaigns/:id/enrollments with emails or prospectIds.
• Example: lead replies on WhatsApp via Interakt → n8n workflow unenrolls from WarmySender email campaign to prevent duplicate outreach.
Account Safety (LinkedIn Events):
WarmySender's webhook delivery does not make any LinkedIn API calls on your behalf — payloads come from our database state only. Toggling webhooks on or off doesn't change how often we poll LinkedIn via Unipile; your LinkedIn account's daily action limits are driven by campaign sends / invites / profile views, not by webhook activity. Enable as many webhooks as you need.
Reliability:
- At-least-once delivery — dedupe on the X-Warmy-Event-Id header on your end.
- 10 retry attempts with exponential backoff (1 min → 72 h) on non-2xx responses.
- 5-second timeout per request — set n8n Response Mode to 'Immediately' and process async after.
Best Practices:
- Add error-handling nodes (Error Trigger workflow) for CRM API failures.
- Store API keys in n8n credentials manager — never hardcode.
- Use Idempotency-Key headers on WarmySender create/enroll calls.
- For publicly-reachable n8n, always verify X-Warmy-Signature.
Zapier / Make:
Same pattern — 'Webhooks by Zapier' (Catch Hook) or Make's Webhook trigger. Copy the URL, paste into WarmySender Settings → Webhooks, chain downstream modules. Zapier/Make handle format translation between our JSON and Slack/HubSpot/etc., but for native Slack, see the 'Slack Notifications' guide — our direct Slack auto-format is simpler than going through Zapier.