How to Build an Automated Lead Enrichment and Dynamic Round-Robin Routing Engine Using Make and HubSpot

Building a reliable lead enrichment and automated routing pipeline is essential for modern RevOps teams looking to eliminate manual CRM triage. Speed-to-lead is the single most decisive metric in enterprise B2B sales. Research consistently demonstrates that responding to an inbound enterprise lead within five minutes yields an 8x higher qualification rate than waiting thirty minutes.

Yet, most modern growth organizations still rely on brittle manual handoffs: marketing webhooks dump raw form submissions into a generic CRM bucket, an operations manager manually qualifies the lead, consults a spreadsheet to see whose turn it is to take a call, and reassigns the record in HubSpot.

By the time the sales development representative (SDR) reaches out, the buyer has already booked a demo with a competitor.

This technical runbook details how to architect a production-grade, serverless Lead Enrichment and Dynamic Round-Robin Routing Engine using Make.com, HubSpot CRM, and Make Data Stores.


Technical Architecture & Pipeline Metrics

SpecificationMetric / TechnologyOperational Standard
Ingestion PipelineWebhook / API IntakeSub-second execution (< 800ms)
State StorageMake Data Store (Key-Value)ACID-compliant transactional counter
Routing AlgorithmModulo Arithmetic Round-RobinFair distribution across active SDR pools
Enrichment TierClearbit / Apollo / Abstract APIReal-time domain verification & firmographics
Downstream CRMHubSpot Sales HubDirect Owner ID assignment & Deal creation
Alerting LayerSlack Bot APIRich blocks with one-click CRM deep links

Architectural Flow Diagram

[Custom Webhook: Inbound Lead Intake]
             │
             â–¼
[Tools: Extract Domain & Filter Disposable Emails]
             │
             â–¼
[HTTP: Query Firmographic Enrichment API]
             │
             â–¼
[Router: Tier Qualification Engine]
     ├─── Route A: Enterprise Tier (ARR > $50M or Employees > 250)
     │         │
     │         ▼
     │    [Data Store: Fetch & Increment Enterprise SDR Index]
     │         │
     │         ▼
     │    [HubSpot: Upsert Contact & Assign Enterprise SDR]
     │         │
     │         ▼
     │    [Slack: Dispatch Urgent High-Priority Alert]
     │
     └─── Route B: Mid-Market Tier (Standard Routing)
               │
               â–¼
          [Data Store: Fetch & Increment Mid-Market SDR Index]
               │
               â–¼
          [HubSpot: Upsert Contact & Assign Mid-Market SDR]
HubSpot Lead Enrichment and Routing Scenario in Make
Figure 1: Production pipeline showing webhook ingestion, domain sanitization, enrichment API, Data Store routing, HubSpot sync, and Slack notification.

Prerequisites & Data Contract

To deploy this scenario, ensure your environment satisfies the following configurations:

  1. Make.com: Core or Pro plan (Data Stores enabled).
  2. HubSpot CRM: API access with crm.objects.contacts.write and crm.objects.deals.write scopes.
  3. Data Store Structure: A dedicated data store named SDR_Routing_State with two key records:
    • Key: enterprise_index (Type: Number, Initial Value: 1)
    • Key: midmarket_index (Type: Number, Initial Value: 1)

Inbound Ingestion Contract (JSON Payload)

Configure your intake forms (Webflow, Typeform, Chili Piper, or custom React frontend) to dispatch payloads matching this schema:

{
  "submission_id": "sub_98432a10",
  "first_name": "Jordan",
  "last_name": "Miller",
  "work_email": "jordan.miller@cloudscale.io",
  "company_name": "CloudScale Systems",
  "company_domain": "cloudscale.io",
  "annual_revenue": "$20M - $50M",
  "use_case_notes": "Evaluating workflow automation for our 400-person support division.",
  "timestamp": "2026-09-12T08:30:00Z"
}
}

Step 1: Webhook Ingestion & Domain Normalization

Raw email inputs frequently introduce formatting inconsistencies (spaces, trailing punctuation, or capitalization).

  1. Add a Custom Webhook module in Make named Inbound B2B Lead Webhook.
  2. Attach a Tools > Set Variable module to sanitize the input and extract the corporate domain:
    • Variable Name: clean_domain
    • Variable Value: Use Make’s text manipulation functions to isolate the domain part of the email:
lower(get(split(1.work_email; "@"); 2))

3.Set a second variable clean_email:

trim(lower(1.work_email))

Step 2: Domain Screening & Automated Lead Enrichment

Submitting raw inbound email addresses directly to an enrichment API (e.g., Clearbit, Apollo, or ZoomInfo) without domain validation leads to two costly failure modes:

  1. API Credit Waste: Paid enrichment tokens are burned attempting to enrich freemail domains like gmail.com or yahoo.com.
  2. CRM Data Corruption: Querying a corporate enrichment endpoint with alex@gmail.com causes the provider to return Google LLC corporate headquarters data (100,000+ employees, Mountain View CA), overwriting actual lead account data in HubSpot.

To prevent this, place a Make.com Router immediately after the inbound webhook to separate corporate domains from free or disposable webmail.

1. Domain Extraction & Normalization

Inside a Tools > Set Variable module, extract the root domain from the submitted email address using Make’s string parser:

Variable Name:
lead_domain

Variable Value:
{{lower(last(split(1.work_email; "@")))}}

2. Configure the Router Filter Gates

Create two execution paths downstream of the router:

Route A: Corporate Leads (Proceed to Enrichment)

Set the filter condition on Route A to exclude known consumer and temporary email providers using a case-insensitive regular expression:

  • Condition: {{lead_domain}}
  • Operator: Does not match regex
  • Pattern: ^(gmail|yahoo|hotmail|outlook|live|icloud|me|proton|protonmail|aol|zoho|tempmail|guerrillamail|mailinator)\.[a-z.]{2,}$

Only leads passing this gate trigger the HTTP > Make an API Key Request module (or native Apollo/Clearbit module) to enrich company size, industry, tech stack, and annual revenue.

Route B: Free / Disposable Leads (Bypass Enrichment)

Set Route B as the fallback path (or invert the regex above). Leads taking this path bypass paid API calls entirely:

  • Default company_name to "Independent / Unassigned"
  • Default employee_count to "Unknown"
  • Route directly to HubSpot contact creation and assign a lower lead priority score for SDR triage.

3. Enrichment API Payload with Safe Fallbacks

For Route A, query your enrichment provider using the parsed domain. If querying an endpoint directly via HTTP module, ensure proper error handling so missing company records (HTTP 404) do not halt scenario execution:

{
  "domain": "{{lead_domain}}",
  "webhook_url": "{{env.ENRICHMENT_CALLBACK_URL}}",
  "enrich_technographics": true
}

Production Directive on Missing Attributes:

Always wrap downstream HubSpot property mappings in {{ifempty()}} formulas. If an enrichment provider matches a domain but lacks specific firmographic attributes, this prevents Make from passing null or empty strings that could wipe existing CRM properties:


HubSpot Property: Number of Employees
Value: {{ifempty(2.body.company.metrics.employees; "Unknown")}}

HubSpot Property: Industry
Value: {{ifempty(2.body.company.category.industry; "Technology")}}

Step 3: Configure the Modulo Round-Robin Engine

To distribute qualified leads evenly across sales representatives without external round-robin tools, use an atomic counter stored in a Make Data Store paired with modulo arithmetic.

1. State Persistence via Make Data Store

Inbound webhooks execute statelessly in parallel. To maintain a unified counter across separate scenario runs:

  1. Add a Data store > Increment a variable module.
  2. Select your internal data store (e.g., RevOps_State_Store).
  3. Set Key to global_lead_counter.
  4. Set Increment value to 1.
  5. This module returns an updated integer (e.g., {{3.value}}) on every inbound execution.

2. The 1-Indexed Modulo Formula

Standard programming modulo arithmetic (counter % N) outputs an index from 0 to N-1. However, Make.com array indices are 1-based, not 0-based. If a modulo formula returns 0, querying get(reps; 0) yields null, leaving the lead unassigned.

To generate clean cyclical values (1, 2, 3) across $N = 3$ representatives, use an offset modulo calculation in a Tools > Set Variable module:

Variable Name:
assigned_rep_index

Variable Value:
{{mod(3.value - 1; 3) + 1}}

How the index cycles:

  • Lead #1: mod(1 - 1; 3) + 1 = 0 + 1 = 1
  • Lead #2: mod(2 - 1; 3) + 1 = 1 + 1 = 2
  • Lead #3: mod(3 - 1; 3) + 1 = 2 + 1 = 3
  • Lead #4: mod(4 - 1; 3) + 1 = 0 + 1 = 1

3. Resolving Representative Metadata

Map the resulting index to an array of your sales team’s HubSpot Owner IDs and Slack Member IDs.

Create a static array inside a Tools > Set Multiple Variables module or configure a simple switch() formula inside the downstream HubSpot module:

{{switch(
  assigned_rep_index;
  1; "58920194";
  2; "61049281";
  3; "74920482";
  "58920194"
)}}

(Note: Replace the numeric strings above with your team’s production HubSpot Owner IDs. The final value acts as a safe fallback default).signments, survives scenario restarts, and ensures balanced lead volume across reps.

Step 4: Multipart Record Creation and Object Association

Once the lead is enriched and the owner index resolved, execute atomic record creation in HubSpot using native API v3 associations.

1. HubSpot > Create a Company (Find or Create)

Prevent duplicate accounts by running a lookup before creation:

  • Search Property: domain equals {{lead_domain}}
  • If Not Found: Create Company with properties:
    • Name: {{ifempty(2.body.company.name; 1.company_name)}}
    • Domain: {{lead_domain}}
    • Number of Employees: {{ifempty(2.body.company.metrics.employees; "Unknown")}}
    • Owner ID: {{assigned_rep_hubspot_id}}

2. HubSpot > Create a Contact with Association

In the HubSpot > Create a Contact module, populate core identity fields and map the relationship immediately:

  • Email: {{1.work_email}}
  • First Name: {{1.first_name}}
  • Last Name: {{1.last_name}}
  • Contact Owner: {{assigned_rep_hubspot_id}}
  • Associated Objects:
    • Association Type: Contact to Company
    • Associated Object ID: {{4.company_id}}
    • Association Category: HUBSPOT_DEFINED (Type ID: 1)

Architecture Note: Mapping the Company ID during contact ingestion avoids executing a secondary, rate-limited batch association API call downstream.

Once the contact is created and assigned, send a real-time notification to your team’s designated triage or sales channel via the Slack > Create a Message module.

Slack does not support standard Markdown hyperlink syntax ([Anchor](URL)). Instead, it uses formatted angle brackets: <URL|Anchor Text>.

Ensure all dynamic links embedded in your Slack payload follow this pattern so reps can open CRM records directly in their browser without copy-pasting IDs.

2. Production Block Kit Payload

In your Slack > Create a Message module, set Text or pass the following JSON payload into the message blocks configuration:

{
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "🎯 New Qualified Lead Assigned",
        "emoji": true
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": "*Lead:*\n{{1.first_name}} {{1.last_name}}\n(<mailto:{{1.work_email}}|{{1.work_email}}>)"
        },
        {
          "type": "mrkdwn",
          "text": "*Company:*\n{{2.company_name}}\n({{ifempty(2.employee_count; "Size Unknown")}} employees)"
        },
        {
          "type": "mrkdwn",
          "text": "*Assigned Rep:*\n<@{{3.slack_user_id}}>"
        },
        {
          "type": "mrkdwn",
          "text": "*Routing Tier:*\n{{ifempty(2.industry; "General Inbound")}}"
        }
      ]
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "Open in HubSpot CRM ↗"
          },
          "url": "https://app.hubspot.com/contacts/{{env.HUBSPOT_PORTAL_ID}}/record/0-1/{{4.hubspot_contact_id}}",
          "style": "primary"
        }
      ]
    }
  ]
}
  • HubSpot Record URLs: Standardize on the modern HubSpot URL format: [https://app.hubspot.com/contacts/](https://app.hubspot.com/contacts/){PORTAL_ID}/record/0-1/{CONTACT_ID} (0-1 designates the Contact object type).
  • Direct Mentions: Use <@{{3.slack_user_id}}> rather than raw names so the assigned sales representative receives an immediate desktop/mobile push notification upon lead ingestion.
Failure ModeRoot CauseArchitectural Mitigation
Enrichment API 429Rapid form submissions exceeding rate limitsConfigure a Break Error Directive with 3 attempts and exponential backoff (15s, 60s, 300s).
Concurrency CollisionsTwo leads arriving within identical 10ms intervalsEnable sequential scenario execution in Make settings (Max active runs: 1).
Rep Absence / PTOAssigned SDR is on scheduled leaveMaintain an active_reps string array in Data Stores; filter out inactive IDs prior to modulo calculation.
Existing Customer Re-entryChurned or existing account filling an inbound formPre-check HubSpot Contact existence before firing round-robin assignment.

Production Testing & Staging Checklist

Prior to switching your webhook endpoint from staging to production, verify:

  • [ ] Webhook validation returns an immediate 200 OK header to prevent form submission hanging.
  • [ ] Free/disposable email addresses route to a self-serve nurture flow rather than live SDR pools.
  • [ ] Modulo counter resets seamlessly from the maximum rep count back to index 1.
  • [ ] HubSpot Owner ID values correspond exactly to internal numeric portal IDs, not display names.
  • [ ] Slack notifications display working hyperlink anchors directly into the correct HubSpot portal ID.

Integrating Your GTM Infrastructure

Dynamic lead routing forms the foundational intake of your revenue engine. To expand this automated pipeline: