Send a Slack alert when a HubSpot deal changes stage using n8n

low complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • HubSpot private app token with crm.objects.deals.read and crm.schemas.deals.read scopes
  • Slack app with Bot Token (chat:write scope)
  • 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.

Trigger vs. polling

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>"
        }
      ]
    }
  ]
}
Block Kit JSON format

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.stageName is one of Negotiation, Closed Won, Closed Lost

This prevents noise from minor stage movements like Qualification → Discovery.

Step 6: Activate

  1. Click Execute Workflow to test with a real deal stage change
  2. Verify the Slack message appears with correct deal info
  3. 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.