Instantly notify a rep in Slack when a high-intent lead books a demo using n8n

low complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • HubSpot private app token with crm.objects.contacts.read and forms scopes
  • Slack app with Bot Token (chat:write scope)
  • n8n credentials configured for both HubSpot and Slack
  • A HubSpot form used for demo bookings (or a Calendly/Cal.com form synced to HubSpot)
  • A mapping of HubSpot owner IDs to Slack user IDs

Why n8n?

n8n gives you event-driven demo alerts with a visual workflow builder. The HubSpot Trigger node fires on form submissions, and the Code node handles the owner-to-Slack mapping without external services. Self-hosted n8n is free with unlimited executions — important when every demo booking generates an alert regardless of volume.

The trade-off vs. Zapier is setup time (slightly more configuration), but you get faster execution, no per-task limits, and full control over the routing logic.

How it works

  • HubSpot Trigger fires on form submissions, then an IF node filters to your demo form
  • HTTP Request fetches the full contact record with enrichment data
  • Code node maps the HubSpot owner ID to a Slack user ID using a lookup dictionary
  • Slack node sends a Block Kit DM to the assigned rep (or a fallback channel for unassigned leads)

Step 1: Add a HubSpot Trigger node

Create a new workflow and add a HubSpot Trigger node:

  • Authentication: Select your HubSpot credential
  • Event: Form Submission

This fires every time someone submits a form in HubSpot. The payload includes the form ID and the contact's email.

Filter to your demo form

HubSpot's form submission webhook fires for ALL forms. Add an IF node after the trigger to check $json.formId === 'YOUR_DEMO_FORM_ID' to only process demo bookings.

Step 2: Filter to the demo form

Add an IF node:

  • Condition: {{ $json.formId }} equals YOUR_DEMO_FORM_ID

Only the "true" branch continues. This prevents alerts for newsletter signups, content downloads, and other forms.

Step 3: Fetch the contact record

Add an HTTP Request node to get the full contact:

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

Step 4: Look up the owner's Slack user ID

Add a Code node to map the HubSpot owner ID to a Slack user ID:

const contact = $('Fetch Contact').first().json;
const props = contact.properties;
 
// HubSpot owner ID → Slack user ID mapping
const ownerToSlack = {
  '12345678': 'U01AAAA',  // Alice
  '23456789': 'U02BBBB',  // Bob
  '34567890': 'U03CCCC',  // Carol
  '45678901': 'U04DDDD',  // Dave
};
 
const ownerId = props.hubspot_owner_id;
const slackUserId = ownerToSlack[ownerId];
 
if (!slackUserId) {
  // No owner or unmapped owner — send to a fallback channel
  return [{ json: { ...props, contactId: contact.id, slackTarget: '#demo-alerts', isFallback: true } }];
}
 
return [{ json: {
  contactId: contact.id,
  name: `${props.firstname || ''} ${props.lastname || ''}`.trim(),
  email: props.email,
  title: props.jobtitle,
  company: props.company,
  employees: props.numberofemployees,
  industry: props.industry,
  source: props.hs_analytics_source,
  slackTarget: slackUserId,
  isFallback: false,
}}];
Unassigned leads

If the lead doesn't have an owner yet (common if routing happens after the form submission), the Slack alert goes to a fallback channel. Chain this with a routing recipe to assign the owner first.

Step 5: Send a Slack DM with Block Kit

Add a Slack node:

  • Resource: Message
  • Operation: Send
  • Channel: {{ $json.slackTarget }}
  • Message Type: Block Kit
{
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "🔥 Demo Booked!" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Name:*\n{{ $json.name }}" },
        { "type": "mrkdwn", "text": "*Title:*\n{{ $json.title || 'Not provided' }}" },
        { "type": "mrkdwn", "text": "*Company:*\n{{ $json.company || 'Unknown' }}" },
        { "type": "mrkdwn", "text": "*Size:*\n{{ $json.employees || 'Unknown' }} employees" },
        { "type": "mrkdwn", "text": "*Industry:*\n{{ $json.industry || 'Unknown' }}" },
        { "type": "mrkdwn", "text": "*Source:*\n{{ $json.source || 'Unknown' }}" }
      ]
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "View Contact" },
          "url": "https://app.hubspot.com/contacts/YOUR_PORTAL_ID/contact/{{ $json.contactId }}",
          "style": "primary"
        }
      ]
    }
  ]
}

Step 6: Activate

  1. Submit your demo form with test data
  2. Verify the Slack DM arrives with the correct contact info
  3. Toggle the workflow to Active

Troubleshooting

Common questions

Should I use a Form Submission trigger or a Contact Property Changed trigger?

Form Submission is more direct — it fires when someone submits a form. But it fires for ALL forms, so you need the IF node to filter. Contact Property Changed on recent_conversion_event_name is an alternative that works even for non-HubSpot forms (Typeform, Calendly) that sync to HubSpot. Choose based on how your demo bookings enter HubSpot.

How fast is the notification?

With the HubSpot Trigger node, notifications arrive within 1-5 seconds of the form submission. This is webhook-based, so there's no polling delay.

What if the lead doesn't have an assigned owner yet?

The Code node falls back to a #demo-alerts channel for unassigned leads. For best results, chain this with a lead routing recipe so the owner is assigned before the alert fires.

How do I handle a large sales team?

The owner-to-Slack mapping in the Code node works for teams up to ~20 reps. For larger teams, store the mapping in a Google Sheet or n8n's built-in data store, and look up the Slack user ID dynamically.

Cost

  • n8n Cloud Starter: $24/mo for 2,500 executions. Each demo booking = 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.