Send a Slack alert when a HubSpot deal changes stage using n8n
Prerequisites
- 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
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
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. Each deal stage change = 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.