Send a Slack alert when a Salesforce deal changes stage using an agent skill
Prerequisites
- Claude Code or another agent that supports the Agent Skills standard
- Salesforce connected app or session token with API access to Opportunities
- Slack bot with
chat:writepermission added to the target channel
Overview
This approach creates an agent skill that queries Salesforce for recently modified opportunities, checks for stage changes, and posts alerts to Slack. Unlike flow-based approaches, this runs on-demand or on a schedule — ideal for a periodic check rather than real-time alerts.
This script finds opportunities modified in the last hour, which may include changes other than stage transitions. Salesforce doesn't expose field-level change history via SOQL on standard objects without using OpportunityFieldHistory. For exact stage change detection, query OpportunityFieldHistory instead.
Step 1: Create the skill
Create .claude/skills/sf-deal-stage-alerts/SKILL.md:
---
name: sf-deal-stage-alerts
description: Check for recent Salesforce opportunity stage changes and post alerts to Slack
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Check for Salesforce opportunities that changed stage in the last hour and post alerts to Slack.
Run: `python $SKILL_DIR/scripts/check_stages.py`The key settings:
disable-model-invocation: true— the skill has external side effects (posting to Slack), so it only runs when you explicitly invoke it with/sf-deal-stage-alertsallowed-tools: Bash(python *)— restricts execution to Python scripts only, preventing unintended shell commands
Step 2: Write the script
Create .claude/skills/sf-deal-stage-alerts/scripts/check_stages.py:
#!/usr/bin/env python3
import os, sys, requests
INSTANCE_URL = os.environ.get("SALESFORCE_INSTANCE_URL")
ACCESS_TOKEN = os.environ.get("SALESFORCE_ACCESS_TOKEN")
SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
SLACK_CHANNEL = os.environ.get("SLACK_CHANNEL_ID")
if not all([INSTANCE_URL, ACCESS_TOKEN, SLACK_TOKEN, SLACK_CHANNEL]):
print("ERROR: Set SALESFORCE_INSTANCE_URL, SALESFORCE_ACCESS_TOKEN, SLACK_BOT_TOKEN, SLACK_CHANNEL_ID")
sys.exit(1)
SF_HEADERS = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json",
}
# Query recently modified opportunities
soql = (
"SELECT Id, Name, Amount, StageName, LastModifiedDate "
"FROM Opportunity "
"WHERE LastModifiedDate > LAST_N_HOURS:1 AND StageName != null"
)
resp = requests.get(
f"{INSTANCE_URL}/services/data/v59.0/query",
headers=SF_HEADERS,
params={"q": soql},
)
resp.raise_for_status()
records = resp.json().get("records", [])
if not records:
print("No opportunities modified in the last hour")
sys.exit(0)
# Post to Slack
from slack_sdk import WebClient
slack = WebClient(token=SLACK_TOKEN)
for opp in records:
amount = float(opp.get("Amount") or 0)
opp_url = f"{INSTANCE_URL}/{opp['Id']}"
slack.chat_postMessage(
channel=SLACK_CHANNEL,
text=f"Deal updated: {opp['Name']}",
blocks=[
{"type": "section", "text": {"type": "mrkdwn",
"text": f"🔄 *Deal Stage Changed*\n*{opp['Name']}* is in *{opp['StageName']}*\nAmount: ${amount:,.0f}"}},
{"type": "context", "elements": [{"type": "mrkdwn",
"text": f"<{opp_url}|View in Salesforce>"}]}
]
)
print(f"Posted {len(records)} deal alerts to Slack")Troubleshooting
What the script does
- Validates environment — checks that
SALESFORCE_INSTANCE_URL,SALESFORCE_ACCESS_TOKEN,SLACK_BOT_TOKEN, andSLACK_CHANNEL_IDare set, exits with a clear error if any are missing - Queries Salesforce — runs a SOQL query via the REST API (
/services/data/v59.0/query) to find Opportunities modified in the last hour that have a stage set - Formats each alert — builds a Block Kit message per opportunity with the deal name, current stage, amount, and a deep link to the Salesforce record
- Posts to Slack — sends one
chat_postMessageper opportunity via the Slack SDK and logs the total number of alerts posted
Step 3: Run it
/sf-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/sf-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 Salesforce flows or Connected Apps for n8n
- You want to run checks on demand during pipeline reviews
Cost
- No Claude API calls — this skill uses direct API calls only
- Salesforce and Slack API usage is included in their respective plans
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.