Route Salesforce leads with round-robin using n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
sf-lead-routing.n8n.jsonPrerequisites
- n8n instance (cloud or self-hosted)
- Salesforce Connected App with OAuth credentials (Consumer Key and Consumer Secret)
- Slack app with Bot Token (
chat:writescope) - n8n credentials configured for both Salesforce and Slack
- A mapping of Salesforce User IDs to Slack User IDs
Why n8n?
n8n gives you a visual canvas where the trigger → assign → update → notify flow is easy to follow and modify. Static data persistence handles the round-robin counter without external storage, and the Code node lets you implement weighted routing or availability checks in a few lines of JavaScript. Self-hosted n8n is free with unlimited executions. The trade-off is a 1-minute polling delay (or more setup for webhook-based real-time triggering) and the need for a Salesforce Connected App with OAuth.
How it works
- Salesforce Trigger node polls for new Lead records every 1 minute (or use an Outbound Message webhook for real-time)
- HTTP Request node fetches the full Lead record including Name, Email, Company, and LeadSource
- Code node reads a round-robin counter from n8n's static data, picks the next rep from a roster array, and increments the counter
- HTTP Request node PATCHes the Lead's OwnerId to the assigned rep in Salesforce
- HTTP Request node creates a follow-up Task on the Lead assigned to the same rep
- Slack node sends a DM to the assigned rep with lead details and a direct link to the Salesforce record
Step 1: Create a Salesforce Connected App
In Salesforce, go to Setup → App Manager → New Connected App:
- Connected App Name:
n8n Lead Router - Enable OAuth Settings: checked
- Callback URL: your n8n OAuth callback URL (shown in n8n's Salesforce credential setup)
- Selected OAuth Scopes:
Full access (full),Perform requests at any time (refresh_token, offline_access)
After saving, copy the Consumer Key and Consumer Secret. In n8n, create a Salesforce credential using these values and complete the OAuth flow.
Salesforce API access requires Enterprise, Unlimited, Developer, or Performance edition. Professional edition users need to purchase the API add-on.
Step 2: Add a Salesforce Trigger node
Create a new workflow and add a Salesforce Trigger node:
- Authentication: Select your Salesforce credential
- Resource: Lead
- Event: Created
- Poll interval: Every minute (or use Outbound Message + Webhook for real-time)
This fires every time a new Lead is created. The payload includes the Lead ID and basic fields.
For instant triggering, create a Salesforce Outbound Message (Workflow Rule or Flow) that sends to an n8n Webhook node. This eliminates the polling delay.
Step 3: Fetch full Lead details
Add an HTTP Request node:
- Method: GET
- URL:
https://YOUR_INSTANCE.salesforce.com/services/data/v59.0/sobjects/Lead/{{ $json.Id }} - Authentication: Predefined → Salesforce OAuth2
- Headers:
Content-Type: application/json
This retrieves the full Lead record including Name, Email, Company, LeadSource, and OwnerId.
Step 4: Round-robin assignment with a Code node
Add a Code node. n8n's static data persists across executions, making it perfect for tracking the round-robin counter:
// Rep roster: Salesforce User ID → Slack User ID
const reps = [
{ name: "Alice", sfUserId: "005xx0000012345", slackUserId: "U01AAAA" },
{ name: "Bob", sfUserId: "005xx0000023456", slackUserId: "U02BBBB" },
{ name: "Carol", sfUserId: "005xx0000034567", slackUserId: "U03CCCC" },
{ name: "Dave", sfUserId: "005xx0000045678", slackUserId: "U04DDDD" },
];
// Get and increment the counter using n8n static data
const staticData = $getWorkflowStaticData('global');
const currentIndex = staticData.roundRobinIndex || 0;
const assignedRep = reps[currentIndex % reps.length];
staticData.roundRobinIndex = (currentIndex + 1) % reps.length;
const lead = $('Fetch Lead').first().json;
return [{
json: {
leadId: lead.Id,
leadName: `${lead.FirstName || ''} ${lead.LastName || ''}`.trim(),
leadEmail: lead.Email,
company: lead.Company,
leadSource: lead.LeadSource,
assignedRep: assignedRep,
}
}];n8n's $getWorkflowStaticData('global') survives across executions but resets if you re-deploy the workflow from scratch. If the counter resets, the worst case is one round of uneven distribution before it balances out.
Step 5: Update Lead owner in Salesforce
Add an HTTP Request node:
- Method: PATCH
- URL:
https://YOUR_INSTANCE.salesforce.com/services/data/v59.0/sobjects/Lead/{{ $json.leadId }} - Authentication: Predefined → Salesforce OAuth2
- Body (JSON):
{
"OwnerId": "{{ $json.assignedRep.sfUserId }}"
}Step 6: Create a follow-up Task on the Lead
Add another HTTP Request node to create a Task reminding the rep to follow up:
- Method: POST
- URL:
https://YOUR_INSTANCE.salesforce.com/services/data/v59.0/sobjects/Task - Authentication: Predefined → Salesforce OAuth2
- Body (JSON):
{
"Subject": "New lead assigned — review within 5 minutes",
"WhoId": "{{ $json.leadId }}",
"OwnerId": "{{ $json.assignedRep.sfUserId }}",
"Status": "Not Started",
"Priority": "High",
"ActivityDate": "{{ $now.format('yyyy-MM-dd') }}"
}Step 7: Send Slack DM to the assigned rep
Add a Slack node:
- Resource: Message
- Operation: Send
- Channel:
{{ $json.assignedRep.slackUserId }}(DM by user ID) - Message Type: Block Kit
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*New Salesforce Lead Assigned to You*\n*{{ $json.leadName }}* — {{ $json.company || 'Unknown company' }}\nEmail: {{ $json.leadEmail || 'N/A' }}\nSource: {{ $json.leadSource || 'N/A' }}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View in Salesforce" },
"url": "https://YOUR_INSTANCE.lightning.force.com/lightning/r/Lead/{{ $json.leadId }}/view"
}
]
}
]
}To send a Slack DM, use the user ID (e.g., U01AAAA) as the channel value. The bot must have chat:write scope and the user must be in the workspace.
Step 8: Activate
- Click Execute Workflow to test with a new Lead
- Verify the Lead's OwnerId was updated in Salesforce
- Check that the follow-up Task was created
- Confirm the Slack DM arrived
- Toggle the workflow to Active
To assign leads proportionally (e.g., senior reps get 2x more), modify the Code node. Replace the flat roster with weighted entries — repeat a rep's entry to increase their share of the rotation.
Troubleshooting
Common questions
How fast does a new Lead get assigned?
With the default 1-minute polling interval, a Lead created at 2:00 PM is typically assigned by 2:01 PM. For instant assignment (under 5 seconds), create a Salesforce Outbound Message that sends to an n8n Webhook node instead of using the polling trigger.
What happens if n8n goes down — do Leads pile up unassigned?
Leads stay with their default owner (or in a queue if you have an assignment rule). When n8n comes back, the Salesforce Trigger picks up where it left off and processes any Leads created during the downtime.
Can I route based on territory or lead source instead of round-robin?
Yes. Add an IF node or Switch node after the trigger to branch by region, lead source, or company size. Each branch can have its own rep roster in its Code node.
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. Each new Lead = 1 execution.
- Self-hosted: Free. Unlimited executions.
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.