Send a Slack alert when a HubSpot deal changes stage using an agent skill
low complexityCost: Usage-based
Prerequisites
Prerequisites
- Claude Code or another agent that supports the Agent Skills standard
- HubSpot private app token stored as
HUBSPOT_TOKENenvironment variable - Slack Bot Token stored as
SLACK_BOT_TOKENenvironment variable - Slack channel ID stored as
SLACK_CHANNEL_IDenvironment variable
Overview
This approach creates an agent skill that checks for recent deal stage changes and posts alerts to Slack. Unlike the webhook-based approaches, this runs on-demand or on a schedule — ideal for a periodic check rather than real-time alerts.
Step 1: Create the skill
Create .claude/skills/deal-stage-alerts/SKILL.md:
---
name: deal-stage-alerts
description: Check for recent HubSpot deal stage changes and post alerts to Slack
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Check for HubSpot deals that changed stage in the last hour and post alerts to Slack.
Run: `python $SKILL_DIR/scripts/check_stages.py`Step 2: Write the script
Create .claude/skills/deal-stage-alerts/scripts/check_stages.py:
#!/usr/bin/env python3
import os, sys, requests
from datetime import datetime, timedelta, timezone
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
SLACK_CHANNEL = os.environ.get("SLACK_CHANNEL_ID")
if not all([HUBSPOT_TOKEN, SLACK_TOKEN, SLACK_CHANNEL]):
print("ERROR: Set HUBSPOT_TOKEN, SLACK_BOT_TOKEN, SLACK_CHANNEL_ID")
sys.exit(1)
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
# Get pipeline stages
stages_resp = requests.get("https://api.hubapi.com/crm/v3/pipelines/deals", headers=HEADERS)
stages_resp.raise_for_status()
stage_map = {}
for p in stages_resp.json()["results"]:
for s in p["stages"]:
stage_map[s["id"]] = s["label"]
# Find recently modified deals
one_hour_ago = int((datetime.now(timezone.utc) - timedelta(hours=1)).timestamp() * 1000)
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/deals/search",
headers=HEADERS,
json={
"filterGroups": [{"filters": [
{"propertyName": "hs_lastmodifieddate", "operator": "GTE", "value": str(one_hour_ago)}
]}],
"properties": ["dealname", "amount", "dealstage"],
"limit": 100
}
)
resp.raise_for_status()
deals = resp.json().get("results", [])
if not deals:
print("No deals modified in the last hour")
sys.exit(0)
# Post to Slack
from slack_sdk import WebClient
slack = WebClient(token=SLACK_TOKEN)
for deal in deals:
props = deal["properties"]
stage_name = stage_map.get(props.get("dealstage", ""), props.get("dealstage", "Unknown"))
amount = float(props.get("amount") or 0)
slack.chat_postMessage(
channel=SLACK_CHANNEL,
text=f"Deal updated: {props['dealname']}",
blocks=[
{"type": "section", "text": {"type": "mrkdwn",
"text": f"🔄 *Deal Stage Changed*\n*{props['dealname']}* is in *{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>"}]}
]
)
print(f"Posted {len(deals)} deal alerts to Slack")Step 3: Run it
/deal-stage-alertsStep 4: Schedule (optional)
For hourly checks, schedule via Cowork or cron:
# crontab — run every hour
0 * * * * cd /path/to/project && python .claude/skills/deal-stage-alerts/scripts/check_stages.pyWhen to use this approach
- You want periodic digest-style alerts, not real-time
- You don't want to maintain a webhook server
- You want to run checks on demand during pipeline reviews
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.