Route Salesforce leads with round-robin using n8n

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • Salesforce Connected App with OAuth credentials (Consumer Key and Consumer Secret)
  • Slack app with Bot Token (chat:write scope)
  • 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.

API access edition requirement

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.

Real-time alternative

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,
  }
}];
Static data persistence

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"
        }
      ]
    }
  ]
}
DM via user ID

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

  1. Click Execute Workflow to test with a new Lead
  2. Verify the Lead's OwnerId was updated in Salesforce
  3. Check that the follow-up Task was created
  4. Confirm the Slack DM arrived
  5. Toggle the workflow to Active
Weighted routing

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.