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

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.

Cost

  • n8n Cloud Starter: $24/mo for 2,500 executions. Each new Lead = 1 execution.
  • Self-hosted: Free. Unlimited executions.

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.