Notify a rep in Slack when a high-intent lead books a demo using a Claude Code skill
Install this skill
Download the skill archive and extract it into your .claude/skills/ directory.
demo-alerts.skill.zipPrerequisites
This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.
- One of the agents listed above
- HubSpot private app with
crm.objects.contacts.readscope - Slack app with
chat:writepermission added to target channels
Why a Claude Code skill?
The other approaches in this guide are automated: they fire on every form submission the same way. An Claude Code skill is different. You tell Claude what you want in plain language, and the skill gives it enough context to do it reliably.
That means you can say:
- "Check for demo bookings in the last hour and alert the reps"
- "Any demo bookings over the weekend? Alert the reps and give me a summary"
- "Check for demos booked today — only alert for contacts with 200+ employees"
The skill contains workflow guidelines, API reference materials, and a Slack message template that the agent reads on demand. When you invoke the skill, Claude reads these files, writes a script on the fly, runs it, and reports results. If you ask for something different next time — a different lookback window, a filtered segment, a summary without alerting — the agent adapts without you touching any code.
How it works
The skill directory has three parts:
SKILL.md— workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoidreferences/— HubSpot API patterns (contact search, form conversion filtering) and Slack API patterns so the agent calls the right endpointstemplates/— a Slack Block Kit message template for the demo alert
When invoked, the agent reads SKILL.md, consults the reference and template files as needed, writes a Python script, executes it, and reports what it found. The reference files act as guardrails — the agent knows exactly which endpoints to hit and what the responses look like, so it doesn't have to guess.
What is a Claude Code skill?
An Claude Code skill is a reusable command you add to your project that Claude Code can run on demand. Skills live in a .claude/skills/ directory and are defined by a SKILL.md file that tells the agent what the skill does, when to run it, and what tools it's allowed to use.
In this skill, the agent doesn't run a pre-written script. Instead, SKILL.md provides workflow guidelines and points to reference files — API documentation, message templates — that the agent reads to generate and execute code itself. This is the key difference from a traditional script: the agent can adapt its approach based on what you ask for while still using the right APIs and message formats.
Once installed, you can invoke a skill as a slash command (e.g., /demo-alerts), or the agent will use it automatically when you give it a task where the skill is relevant. Skills are portable — anyone who clones your repo gets the same commands.
Step 1: Create the skill directory
mkdir -p .claude/skills/demo-alerts/{templates,references}This creates the layout:
.claude/skills/demo-alerts/
├── SKILL.md # workflow guidelines + config
├── templates/
│ └── slack-alert.md # Block Kit message template
└── references/
└── hubspot-demo-api.md # HubSpot contact search + Slack API patternsStep 2: Write the SKILL.md
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, Read
---
## Goal
Check for contacts who recently booked a demo in HubSpot and send a Slack DM to the assigned rep with the contact's details and a link to the HubSpot record. Default: check the last hour. The user may request a different lookback window.
## Configuration
Read these environment variables:
- `HUBSPOT_ACCESS_TOKEN` — HubSpot private app token (required)
- `SLACK_BOT_TOKEN` — Slack bot token with chat:write scope (required)
- `HUBSPOT_PORTAL_ID` — HubSpot portal ID for contact links (required)
- `SLACK_FALLBACK_CHANNEL` — Fallback channel ID when no owner mapping exists (optional, default: #demo-alerts)
The skill also needs an owner-to-Slack mapping. Define it as a dictionary in the script:
```python
OWNER_TO_SLACK = {
"12345678": "U01AAAA", # Rep name
"23456789": "U02BBBB", # Rep name
}
```
## Workflow
1. Validate that `HUBSPOT_ACCESS_TOKEN`, `SLACK_BOT_TOKEN`, and `HUBSPOT_PORTAL_ID` are set. If any are missing, print the error and exit.
2. Calculate the lookback window. Default: 1 hour. The user may say "check the last 24 hours" or similar.
3. Search for contacts whose `recent_conversion_date` falls within the lookback window. See `references/hubspot-demo-api.md`.
4. Filter results to contacts whose `recent_conversion_event_name` contains "demo" (case-insensitive).
5. For each demo booking, resolve the Slack target by looking up the `hubspot_owner_id` in the owner-to-Slack mapping. Fall back to the `SLACK_FALLBACK_CHANNEL` env var if not mapped.
6. Send a Slack Block Kit message to each target using the template in `templates/slack-alert.md`. See `references/hubspot-demo-api.md` for the Slack API endpoint.
7. Print a summary: how many demo bookings found, how many alerts sent, and which reps were notified.
## Important notes
- HubSpot's `recent_conversion_date` is in milliseconds since epoch. Multiply Python's `time.time()` by 1000.
- The `recent_conversion_event_name` property contains the form name. Filter for forms that contain "demo" in the name — this avoids alerting for newsletter signups or content downloads.
- Owner-to-Slack mapping must be updated with real HubSpot owner IDs and Slack user IDs. Find owner IDs in HubSpot > Settings > Users & Teams. Find Slack user IDs by clicking a profile > three dots > Copy member ID.
- Use the `requests` library for HTTP calls. Install with pip if needed.
- HubSpot allows 150 API requests per 10 seconds. Demo bookings are typically low-volume, so rate limits are rarely an issue.
- Paginate search results using the `after` cursor from `paging.next.after`.Understanding the SKILL.md
| Section | Purpose |
|---|---|
| Goal | Tells the agent what outcome to produce |
| Configuration | Which env vars to read and how to configure owner mapping |
| Workflow | Numbered steps with pointers to reference files |
| Important notes | Non-obvious context that prevents common mistakes |
Step 3: Add reference files
references/hubspot-demo-api.md
Create .claude/skills/demo-alerts/references/hubspot-demo-api.md:
# HubSpot Demo Bookings + Slack API Reference
## Authentication
### HubSpot
```
Authorization: Bearer <HUBSPOT_ACCESS_TOKEN>
Content-Type: application/json
```
### Slack
```
Authorization: Bearer <SLACK_BOT_TOKEN>
Content-Type: application/json
```
## Search for recent form conversions
**Request:**
```
POST https://api.hubapi.com/crm/v3/objects/contacts/search
Authorization: Bearer <token>
Content-Type: application/json
```
**Body (contacts converted in the last N milliseconds):**
```json
{
"filterGroups": [
{
"filters": [
{
"propertyName": "recent_conversion_date",
"operator": "GTE",
"value": "<epoch_ms>"
}
]
}
],
"properties": [
"firstname", "lastname", "email", "jobtitle", "company",
"numberofemployees", "industry", "hubspot_owner_id",
"recent_conversion_event_name", "hs_analytics_source"
],
"limit": 100
}
```
**Response shape:**
```json
{
"total": 12,
"results": [
{
"id": "12345",
"properties": {
"firstname": "Alex",
"lastname": "Rivera",
"email": "alex@techcorp.com",
"jobtitle": "VP of Engineering",
"company": "TechCorp",
"numberofemployees": "500",
"industry": "computer software",
"hubspot_owner_id": "12345678",
"recent_conversion_event_name": "Book a Demo",
"hs_analytics_source": "ORGANIC_SEARCH"
}
}
],
"paging": {
"next": {
"after": "100"
}
}
}
```
Filter results where `recent_conversion_event_name` contains "demo" (case-insensitive) to separate demo bookings from other form submissions.
Paginate using `after` cursor until `paging.next` is absent.
## Send a Slack message
**Request:**
```
POST https://slack.com/api/chat.postMessage
Authorization: Bearer <SLACK_BOT_TOKEN>
Content-Type: application/json
```
**Body:**
```json
{
"channel": "<slack_user_id_or_channel_id>",
"text": "Demo booked: Alex Rivera at TechCorp",
"blocks": [...]
}
```
The `text` field is the fallback for notifications. The `blocks` array contains the rich Block Kit message. See `templates/slack-alert.md` for the block structure.
**Response shape:**
```json
{
"ok": true,
"channel": "D12345678",
"ts": "1234567890.123456"
}
```
If `ok` is false, check the `error` field for details (e.g., `channel_not_found`, `not_in_channel`).
## Rate limits
- HubSpot: 150 requests per 10 seconds for private apps
- Slack: 1 message per second per channel (Tier 3)templates/slack-alert.md
Create .claude/skills/demo-alerts/templates/slack-alert.md:
# Slack Demo Alert Template
Use this Block Kit structure for each demo booking alert.
## Block Kit JSON
```json
[
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🔥 Demo Booked!"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Name:*\n<name>"
},
{
"type": "mrkdwn",
"text": "*Title:*\n<jobtitle>"
},
{
"type": "mrkdwn",
"text": "*Company:*\n<company> (<numberofemployees> employees)"
},
{
"type": "mrkdwn",
"text": "*Industry:*\n<industry>"
},
{
"type": "mrkdwn",
"text": "*Source:*\n<hs_analytics_source>"
},
{
"type": "mrkdwn",
"text": "*Email:*\n<email>"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Contact"
},
"url": "https://app.hubspot.com/contacts/<PORTAL_ID>/contact/<contact_id>",
"style": "primary"
}
]
}
]
```
## Substitution values
| Placeholder | Source |
|-------------|--------|
| `<name>` | `firstname` + `lastname` from contact properties |
| `<jobtitle>` | `jobtitle` property, default "N/A" |
| `<company>` | `company` property, default "Unknown" |
| `<numberofemployees>` | `numberofemployees` property, default "?" |
| `<industry>` | `industry` property, default "Unknown" |
| `<hs_analytics_source>` | `hs_analytics_source` property, default "Unknown" |
| `<email>` | `email` property, default "N/A" |
| `<PORTAL_ID>` | From `HUBSPOT_PORTAL_ID` env var |
| `<contact_id>` | Contact `id` from search results |
## Fallback text
Set the `text` field to: `"Demo booked: <name> at <company>"` — this shows in Slack notifications and mobile previews.Step 4: Test the skill
Invoke the skill conversationally:
/demo-alertsClaude will read the SKILL.md, check the reference files, write a script, run it, and report the results. A typical run looks like:
Checking for demo bookings in the last hour...
Found 2 demo bookings
Alex Rivera (VP of Engineering at TechCorp) → DM to Sarah Chen
Jordan Kim (Director of Ops at GrowthCo) → DM to Mike Johnson
Done. Sent 2 alerts.Because the agent generates code on the fly, you can also make ad hoc requests:
- "Check for demo bookings over the weekend" — the agent adjusts the lookback window
- "Any demos today from companies with 500+ employees?" — the agent adds a filter
- "Just give me a summary, don't send Slack alerts" — the agent reports without posting
Make sure you have at least one contact in HubSpot whose recent_conversion_event_name contains "demo" and whose recent_conversion_date is within the lookback window. Otherwise the skill will report zero bookings.
Step 5: Schedule it (optional)
Option A: Cron + Claude CLI
# Check for demo bookings every 15 minutes
*/15 * * * * cd /path/to/your/project && claude -p "Run /demo-alerts" --allowedTools 'Bash(*)' 'Read(*)'Option B: GitHub Actions + Claude
name: Demo Alerts
on:
schedule:
- cron: '*/15 * * * *' # Every 15 minutes
workflow_dispatch: {} # Manual trigger for testing
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
prompt: "Run /demo-alerts"
allowed_tools: "Bash(*),Read(*)"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
HUBSPOT_ACCESS_TOKEN: ${{ secrets.HUBSPOT_ACCESS_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
HUBSPOT_PORTAL_ID: ${{ secrets.HUBSPOT_PORTAL_ID }}Option C: Cowork Scheduled Tasks
Claude Desktop's Cowork supports built-in scheduled tasks. Open a Cowork session, type /schedule, and configure the cadence — hourly, daily, weekly, or weekdays only. Each scheduled run has full access to your connected tools, plugins, and MCP servers.
Scheduled tasks only run while your computer is awake and Claude Desktop is open. If a run is missed, Cowork executes it automatically when the app reopens. For always-on scheduling, use GitHub Actions (Option B) instead. Available on all paid plans (Pro, Max, Team, Enterprise).
Troubleshooting
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
- You need to filter by specific criteria (company size, industry) on the fly
When to switch approaches
- You need real-time alerts within seconds of the form submission → use n8n or Code + webhook
- You want a visual workflow builder → use n8n or Make
- You want the fastest setup → use Zapier
Common questions
Why not just use a script?
A script runs the same way every time. The Claude Code skill adapts to what you ask — different lookback windows, filtered segments, summary-only mode, different alert formats. The reference files ensure it calls the right APIs even when improvising, so you get flexibility without sacrificing reliability.
Does this use Claude API credits?
Yes. Unlike a script-based approach, the agent reads skill files and generates code each time. Typical cost is $0.01-0.05 per invocation. The HubSpot and Slack APIs themselves are free.
Can I use this alongside real-time alerts?
Yes. Use n8n or webhooks for real-time notifications, and the Claude Code skill for periodic sweeps to catch anything that slipped through — missed webhooks, unassigned leads, or weekend bookings nobody saw.
Cost
- Claude API — $0.01-0.05 per invocation (the agent reads files and generates code)
- HubSpot API — included in all plans, no per-call cost
- Slack API — included in all plans
- GitHub Actions (if scheduled) — free tier includes 2,000 minutes/month
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.