Notify a rep in Slack when a high-intent lead books a demo 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
Overview
This approach creates an agent skill that checks for recent demo bookings and alerts the assigned reps in Slack. Unlike the webhook-based approaches, this runs on-demand or on a schedule -- useful as a periodic check or a pre-meeting prep tool.
Step 1: Create the skill
Create .claude/skills/demo-alerts/SKILL.md:
---
name: demo-alerts
description: Check for recent HubSpot demo bookings and alert reps in Slack
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Check for contacts who recently booked a demo and send Slack alerts to their assigned reps.
Usage:
- `/demo-alerts` — check the last hour for new demo bookings
- `/demo-alerts --hours 24` — check the last 24 hours
Run: `python $SKILL_DIR/scripts/check_demos.py $@`Step 2: Write the script
Create .claude/skills/demo-alerts/scripts/check_demos.py:
#!/usr/bin/env python3
"""Check for recent demo bookings and alert reps in Slack."""
import os, sys, requests, argparse
from datetime import datetime, timedelta, timezone
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
if not all([HUBSPOT_TOKEN, SLACK_TOKEN]):
print("ERROR: Set HUBSPOT_TOKEN and SLACK_BOT_TOKEN")
sys.exit(1)
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
OWNER_TO_SLACK = {
"12345678": "U01AAAA",
"23456789": "U02BBBB",
"34567890": "U03CCCC",
"45678901": "U04DDDD",
}
FALLBACK_CHANNEL = os.environ.get("SLACK_FALLBACK_CHANNEL", "#demo-alerts")
PORTAL_ID = os.environ.get("HUBSPOT_PORTAL_ID", "YOUR_PORTAL_ID")
parser = argparse.ArgumentParser()
parser.add_argument("--hours", type=int, default=1)
args = parser.parse_args()
since = int((datetime.now(timezone.utc) - timedelta(hours=args.hours)).timestamp() * 1000)
# Search for recent form conversions
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts/search",
headers=HEADERS,
json={
"filterGroups": [{"filters": [
{"propertyName": "recent_conversion_date", "operator": "GTE", "value": str(since)}
]}],
"properties": ["firstname","lastname","email","jobtitle","company","numberofemployees",
"industry","hubspot_owner_id","recent_conversion_event_name","hs_analytics_source"],
"limit": 100
}
)
resp.raise_for_status()
contacts = resp.json().get("results", [])
# Filter for demo bookings
demos = [c for c in contacts if "demo" in (c["properties"].get("recent_conversion_event_name") or "").lower()]
if not demos:
print(f"No demo bookings in the last {args.hours} hour(s)")
sys.exit(0)
print(f"Found {len(demos)} demo booking(s). Sending alerts...")
from slack_sdk import WebClient
slack = WebClient(token=SLACK_TOKEN)
for contact in demos:
props = contact["properties"]
name = f"{props.get('firstname', '')} {props.get('lastname', '')}".strip()
owner_id = props.get("hubspot_owner_id")
slack_target = OWNER_TO_SLACK.get(owner_id, FALLBACK_CHANNEL)
slack.chat_postMessage(
channel=slack_target,
text=f"Demo booked: {name} at {props.get('company', 'Unknown')}",
blocks=[
{"type": "header", "text": {"type": "plain_text", "text": "🔥 Demo Booked!"}},
{"type": "section", "fields": [
{"type": "mrkdwn", "text": f"*Name:*\n{name}"},
{"type": "mrkdwn", "text": f"*Title:*\n{props.get('jobtitle', 'N/A')}"},
{"type": "mrkdwn", "text": f"*Company:*\n{props.get('company', 'Unknown')} ({props.get('numberofemployees', '?')} emp)"},
{"type": "mrkdwn", "text": f"*Source:*\n{props.get('hs_analytics_source', 'Unknown')}"},
]},
{"type": "actions", "elements": [
{"type": "button", "text": {"type": "plain_text", "text": "View Contact"},
"url": f"https://app.hubspot.com/contacts/{PORTAL_ID}/contact/{contact['id']}",
"style": "primary"}
]}
]
)
print(f" Alerted {slack_target} about {name}")
print(f"Done. Sent {len(demos)} alert(s).")Step 3: Run it
# Check the last hour
/demo-alerts
# Check the last 24 hours (e.g., Monday morning catch-up)
/demo-alerts --hours 24When to use this approach
- You want a periodic check for demo bookings rather than real-time webhooks
- You're doing a Monday morning sweep to make sure no weekend demos were missed
- You want to run a quick check before a pipeline review meeting
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.