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_TOKEN environment variable
  • Slack Bot Token stored as SLACK_BOT_TOKEN environment 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 24

When 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.