Send a Slack alert when a HubSpot deal changes stage using n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
deal-stage-alert.n8n.jsonPrerequisites
- n8n instance (cloud or self-hosted)
- HubSpot private app token with
crm.objects.deals.readandcrm.schemas.deals.readscopes - Slack app with Bot Token (
chat:writescope) - n8n credentials configured for both HubSpot and Slack
Why n8n?
n8n is the best option if you want full control without ongoing costs. Self-hosted n8n is completely free with unlimited executions — you only pay for your own infrastructure. The visual workflow editor makes it easy to see exactly what's happening at each step, and you can modify the logic without touching code.
The HubSpot Trigger node uses webhooks, so alerts arrive in seconds rather than minutes. If you're already running n8n for other automations, adding this workflow takes under 20 minutes. If you're not, the initial setup (Docker or npm install) adds another 15 minutes.
How it works
- HubSpot Trigger node listens for
deal.propertyChangeevents on thedealstageproperty via webhook — fires within seconds of a stage change - HTTP Request node fetches the full deal record (name, amount, owner) since the trigger only sends the deal ID and changed property
- HTTP Request node calls the Pipelines API to get stage ID-to-label mappings
- Code node resolves the internal stage ID (e.g.
closedwon) to the display label (e.g. "Closed Won") - Slack node posts a Block Kit message with deal details and a direct link to the HubSpot record
Step 1: Add a HubSpot Trigger node
Create a new workflow and add a HubSpot Trigger node:
- Authentication: Select your HubSpot credential
- Event: Deal Property Changed
- Property:
dealstage
This fires every time any deal's stage property is updated. HubSpot sends the deal ID and the new stage value via webhook.
The HubSpot Trigger node uses webhooks for near-instant delivery (1-5 seconds). This is faster than polling, which checks every 1-5 minutes depending on your plan.
Step 2: Fetch full deal details
The trigger only sends the deal ID and changed property. Add an HTTP Request node to get the full deal record:
- Method: GET
- URL:
https://api.hubapi.com/crm/v3/objects/deals/{{ $json.objectId }} - Authentication: Predefined → HubSpot API
- Query params:
properties=dealname,amount,dealstage,pipeline,hubspot_owner_id,closedate
Step 3: Resolve stage name
The dealstage property returns an ID like closedwon, not a label. Add another HTTP Request node:
- Method: GET
- URL:
https://api.hubapi.com/crm/v3/pipelines/deals
Then add a Code node to map the stage ID to its label:
const pipelines = $('Fetch Pipelines').first().json.results;
const deal = $('Fetch Deal').first().json;
let stageName = deal.properties.dealstage;
for (const pipeline of pipelines) {
const stage = pipeline.stages.find(s => s.id === deal.properties.dealstage);
if (stage) {
stageName = stage.label;
break;
}
}
return [{
json: {
dealName: deal.properties.dealname,
amount: parseFloat(deal.properties.amount || '0'),
stageName,
ownerId: deal.properties.hubspot_owner_id,
dealId: deal.id,
}
}];Step 4: Send Slack notification
Add a Slack node:
- Resource: Message
- Operation: Send
- Channel:
#sales-pipeline(or your deals channel) - Message Type: Block Kit
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🔄 *Deal Stage Changed*\n*{{ $json.dealName }}* moved to *{{ $json.stageName }}*\nAmount: ${{ $json.amount.toLocaleString() }}"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "<https://app.hubspot.com/contacts/YOUR_PORTAL_ID/deal/{{ $json.dealId }}|View in HubSpot>"
}
]
}
]
}n8n's Slack node expects the full {"blocks": [...]} wrapper. If you pass just the array, blocks are silently ignored and only the notification text shows.
Step 5: Filter unwanted transitions (optional)
If you don't want alerts for every stage change, add an IF node before the Slack node:
- Condition:
$json.stageNameis one ofNegotiation,Closed Won,Closed Lost
This prevents noise from minor stage movements like Qualification → Discovery.
Step 6: Activate
- Click Execute Workflow to test with a real deal stage change
- Verify the Slack message appears with correct deal info
- Toggle the workflow to Active
Troubleshooting
Common questions
Should I use n8n Cloud or self-hosted for this workflow?
For a single workflow like this, self-hosted is free and straightforward — a $5/mo VPS handles it easily. n8n Cloud ($24/mo starter) makes sense if you're running multiple workflows and don't want to manage infrastructure. The workflow itself is identical on both.
How often does the HubSpot Trigger node poll?
The HubSpot Trigger node in n8n uses webhooks, not polling. Events arrive within 1-5 seconds of the stage change in HubSpot. However, if you're using a self-hosted instance behind a firewall without a public URL, webhooks won't work — you'll need to use n8n's tunnel feature for testing or set up a reverse proxy.
Will this hit HubSpot API rate limits?
Unlikely for deal stage alerts. HubSpot allows 100 requests per 10 seconds for private apps. Each stage change triggers 2 API calls (deal details + pipeline stages). You'd need 50 deals changing stage within 10 seconds to hit the limit — that's not a realistic scenario for most teams. If you have very high volume, cache the pipeline stages response in a Set node so you only fetch it once per execution.
Can I use this with multiple pipelines?
Yes. The workflow automatically handles all pipelines — the HubSpot Trigger fires on stage changes across every pipeline, and the Code node resolves stage names from any pipeline. To filter to a specific pipeline, add an IF node after the Fetch Deal step that checks the pipeline property.
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. Each deal stage change = 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.