Instantly notify a rep in Slack when a high-intent lead books a demo using n8n
Prerequisites
- n8n instance (cloud or self-hosted)
- HubSpot private app token with
crm.objects.contacts.readandformsscopes - Slack app with Bot Token (
chat:writescope) - 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
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.
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 }}equalsYOUR_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,
}}];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
- Submit your demo form with test data
- Verify the Slack DM arrives with the correct contact info
- Toggle the workflow to Active
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. Each demo booking = 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.