Send a Slack alert when a HubSpot deal changes stage using code
Prerequisites
- Node.js 18+ or Python 3.9+
- HubSpot private app token with
crm.objects.deals.readandcrm.schemas.deals.readscopes - Slack Bot Token (
xoxb-...) withchat:writescope - A server or serverless function to receive webhooks (Express, Vercel, AWS Lambda)
Step 1: Subscribe to deal property changes
HubSpot's webhook subscriptions API lets you receive events when deal properties change. Set up a subscription for the dealstage property:
curl -X POST "https://api.hubapi.com/webhooks/v3/YOUR_APP_ID/subscriptions" \
-H "Authorization: Bearer $HUBSPOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventType": "deal.propertyChange",
"propertyName": "dealstage",
"active": true
}'Webhooks are instant (1-5 seconds). If you can't host a webhook endpoint, use polling instead — search for deals modified in the last N minutes on a cron schedule. See the polling alternative at the end.
Step 2: Build the webhook handler
Create an endpoint that receives HubSpot webhook events, fetches the full deal, resolves the stage name, and posts to Slack:
from flask import Flask, request, jsonify
from slack_sdk import WebClient
import requests
import os
app = Flask(__name__)
slack = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
HUBSPOT_TOKEN = os.environ["HUBSPOT_TOKEN"]
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}"}
# Cache pipeline stages on startup
stages_resp = requests.get("https://api.hubapi.com/crm/v3/pipelines/deals", headers=HEADERS)
STAGE_MAP = {}
for pipeline in stages_resp.json()["results"]:
for stage in pipeline["stages"]:
STAGE_MAP[stage["id"]] = stage["label"]
@app.route("/webhook", methods=["POST"])
def handle_webhook():
events = request.json
for event in events:
if event.get("propertyName") != "dealstage":
continue
deal_id = event["objectId"]
new_stage_id = event["propertyValue"]
stage_name = STAGE_MAP.get(new_stage_id, new_stage_id)
# Fetch deal details
deal_resp = requests.get(
f"https://api.hubapi.com/crm/v3/objects/deals/{deal_id}",
headers=HEADERS,
params={"properties": "dealname,amount,hubspot_owner_id"}
)
deal = deal_resp.json()["properties"]
amount = float(deal.get("amount") or 0)
slack.chat_postMessage(
channel=os.environ["SLACK_CHANNEL_ID"],
text=f"Deal stage changed: {deal['dealname']}",
blocks=[
{"type": "section", "text": {"type": "mrkdwn",
"text": f"🔄 *Deal Stage Changed*\n*{deal['dealname']}* moved to *{stage_name}*\nAmount: ${amount:,.0f}"}},
{"type": "context", "elements": [{"type": "mrkdwn",
"text": f"<https://app.hubspot.com/contacts/YOUR_PORTAL_ID/deal/{deal_id}|View in HubSpot>"}]}
]
)
return jsonify({"status": "ok"}), 200Step 3: Deploy and register the webhook URL
Deploy your handler to a publicly accessible URL (Vercel, Railway, AWS Lambda, etc.), then register it with HubSpot:
curl -X PUT "https://api.hubapi.com/webhooks/v3/YOUR_APP_ID/settings" \
-H "Authorization: Bearer $HUBSPOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"targetUrl": "https://your-server.com/webhook"}'Polling alternative
If you can't host a webhook, poll on a schedule instead:
# Run every 5 minutes via cron
# 0/5 * * * * python poll_stage_changes.py
import requests, os, json
from datetime import datetime, timedelta, timezone
from slack_sdk import WebClient
HUBSPOT_TOKEN = os.environ["HUBSPOT_TOKEN"]
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}"}
slack = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
five_min_ago = int((datetime.now(timezone.utc) - timedelta(minutes=5)).timestamp() * 1000)
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/deals/search",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"filterGroups": [{"filters": [{
"propertyName": "hs_lastmodifieddate",
"operator": "GTE",
"value": str(five_min_ago)
}]}],
"properties": ["dealname", "amount", "dealstage"],
"limit": 100
}
)
for deal in resp.json().get("results", []):
# Post to Slack (same Block Kit as above)
passCost
- Hosting: Free on Vercel (serverless), ~$5/mo on Railway, or use GitHub Actions for polling
- No per-execution cost beyond hosting
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.