Round-robin route HubSpot leads and notify reps in Slack using n8n

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • HubSpot private app token with crm.objects.contacts.read, crm.objects.contacts.write, and settings.users.read scopes
  • Slack app with Bot Token (chat:write, users:read scopes)
  • n8n credentials configured for both HubSpot and Slack
  • A mapping of HubSpot owner IDs to Slack user IDs

Why n8n?

n8n is the best option for round-robin routing because its static data feature ($getWorkflowStaticData) persists the counter across executions without external storage. The Code node handles the assignment logic in a single step, and the visual builder makes it easy to add filters (skip already-assigned contacts) or branches (different routing for different lead sources).

Self-hosted n8n is free with unlimited executions — important when every new lead triggers the workflow regardless of volume.

How it works

  • HubSpot Trigger fires on contact creation, then an IF node filters out already-assigned contacts
  • HTTP Request fetches the full contact record with enrichment data
  • Code node reads the round-robin counter from static data, selects the next rep, and increments
  • HTTP Request updates the contact's hubspot_owner_id in HubSpot
  • Slack node sends a Block Kit DM to the assigned rep

Step 1: Add a HubSpot Trigger node

Create a new workflow and add a HubSpot Trigger node:

  • Authentication: Select your HubSpot credential
  • Event: Contact Created

This fires every time a new contact is created in HubSpot. The payload includes the contact ID.

Step 2: Fetch contact details

Add an HTTP Request node:

  • Method: GET
  • URL: https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.objectId }}
  • Authentication: Predefined -> HubSpot API
  • Query params: properties=firstname,lastname,email,company,jobtitle,hubspot_owner_id
Skip already-assigned contacts

If the contact already has a hubspot_owner_id, it was assigned by another workflow or manually. Add an IF node to check $json.properties.hubspot_owner_id is empty before proceeding.

Step 3: 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: HubSpot owner ID → Slack user ID
const reps = [
  { name: "Alice", hubspotOwnerId: "12345678", slackUserId: "U01AAAA" },
  { name: "Bob",   hubspotOwnerId: "23456789", slackUserId: "U02BBBB" },
  { name: "Carol", hubspotOwnerId: "34567890", slackUserId: "U03CCCC" },
  { name: "Dave",  hubspotOwnerId: "45678901", 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 contact = $('Fetch Contact').first().json;
 
return [{
  json: {
    contactId: contact.id,
    contactName: `${contact.properties.firstname || ''} ${contact.properties.lastname || ''}`.trim(),
    contactEmail: contact.properties.email,
    company: contact.properties.company,
    title: contact.properties.jobtitle,
    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 4: Update contact owner in HubSpot

Add an HTTP Request node:

  • Method: PATCH
  • URL: https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.contactId }}
  • Body:
{
  "properties": {
    "hubspot_owner_id": "{{ $json.assignedRep.hubspotOwnerId }}"
  }
}

Step 5: 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 Lead Assigned to You*\n*{{ $json.contactName }}* — {{ $json.title || 'No title' }} at {{ $json.company || 'Unknown company' }}\nEmail: {{ $json.contactEmail }}"
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "View in HubSpot" },
          "url": "https://app.hubspot.com/contacts/YOUR_PORTAL_ID/contact/{{ $json.contactId }}"
        }
      ]
    }
  ]
}
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 6: Activate

  1. Click Execute Workflow to test with a new contact
  2. Verify the contact's owner was set in HubSpot and the Slack DM arrived
  3. Toggle the workflow to Active

Troubleshooting

Common questions

What happens to the counter if the workflow is re-deployed?

n8n's $getWorkflowStaticData('global') survives across executions but resets if you delete and recreate the workflow from scratch. If it resets, the worst case is one round of slightly uneven distribution before it balances out. For critical fairness guarantees, store the counter in a database or Redis instead.

How do I skip reps who are out of office?

Add an activeReps filter in the Code node. Maintain a Google Sheet or n8n credential variable with the list of available rep indices. The Code node reads this list and only assigns to active reps. When someone returns, update the list — no workflow changes needed.

Can I do weighted routing instead of equal round-robin?

Yes. Replace the simple modulo counter with a weighted distribution. For example, give senior reps 2 slots in the rotation array (e.g., [Alice, Alice, Bob, Carol]) so they receive proportionally more leads.

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.