Request discount approval in Slack for HubSpot deals using a Claude Code skill

low complexityCost: Usage-based

Prerequisites

Compatible agents

This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.

Prerequisites
  • One of the agents listed above
  • HubSpot private app with crm.objects.deals.read scope
  • Slack bot with chat:write permission, added to the target channel
  • Custom HubSpot deal properties: discount_percent (number) and discount_approval (dropdown with Approved / Pending / Denied)
Environment Variables
# HubSpot private app token (Settings > Integrations > Private Apps)
HUBSPOT_ACCESS_TOKEN=your_value_here
# Slack bot token starting with xoxb- (chat:write scope required)
SLACK_BOT_TOKEN=your_value_here
# Slack channel ID starting with C (right-click channel > View channel details)
SLACK_CHANNEL_ID=your_value_here

Why a Claude Code skill?

The other approaches in this guide are deterministic: they run the same logic every time, 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:

  • "Audit deals with discounts over 15% and flag unapproved ones to Slack"
  • "Which deals have discounts above 20% this week?"
  • "Post a summary of unapproved discounts to #deal-desk instead"

The skill contains workflow guidelines, API reference materials, and a 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 higher threshold, a different channel, a summary grouped by rep — the agent adapts without you touching any code.

How it works

The skill directory has three parts:

  1. SKILL.md — workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoid
  2. references/ — HubSpot API patterns (endpoints, request shapes, response formats) so the agent calls the right APIs with the right parameters
  3. templates/ — a Slack Block Kit template so messages are consistently formatted across runs

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 posted. 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., /discount-audit), 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/discount-audit/{templates,references}

This creates the layout:

.claude/skills/discount-audit/
├── SKILL.md                          # workflow guidelines + config
├── templates/
│   └── slack-alert.md                # Block Kit template for Slack messages
└── references/
    └── hubspot-deals-api.md          # HubSpot API patterns

Step 2: Write the SKILL.md

Create .claude/skills/discount-audit/SKILL.md:

---
name: discount-audit
description: Find HubSpot deals with unapproved discounts above threshold and post a summary to Slack
disable-model-invocation: true
allowed-tools: Bash, Read
---
 
## Goal
 
Search HubSpot for deals where `discount_percent` exceeds a threshold (default: 15%) and `discount_approval` is not "Approved". Post a consolidated summary to Slack listing each deal that needs manager review.
 
## Configuration
 
Read these environment variables:
 
- `HUBSPOT_ACCESS_TOKEN` — HubSpot private app token (required)
- `SLACK_BOT_TOKEN` — Slack bot token starting with xoxb- (required)
- `SLACK_CHANNEL_ID` — Slack channel ID starting with C (required)
 
Default discount threshold: 15%. The user may request a different threshold.
 
## Workflow
 
1. Validate that all required env vars are set. If any are missing, print which ones and exit.
2. Search for deals with `discount_percent` greater than the threshold using the HubSpot CRM Search API. See `references/hubspot-deals-api.md` for the request and response format.
3. Filter out deals where `discount_approval` is already "Approved" — this must be done client-side since HubSpot's search API has limited NOT_EQUAL support on custom properties.
4. If no unapproved deals remain, print "No unapproved high-discount deals found" and exit.
5. Build a single Slack message listing all unapproved deals using the template in `templates/slack-alert.md`.
6. Post the message to Slack and print a summary of how many deals were flagged.
 
## Important notes
 
- HubSpot's search API uses internal property names, not display labels. The custom properties must be named exactly `discount_percent` and `discount_approval`.
- The search API `GT` filter on `discount_percent` requires the value as a string (e.g., `"15"` not `15`).
- `SLACK_CHANNEL_ID` must be the channel ID (starts with `C`), not the channel name. The `chat.postMessage` API requires the ID.
- The Slack bot must be invited to the target channel or `chat.postMessage` will fail with `not_in_channel`.
- Use `urllib.request` for HTTP calls (no external dependencies required).

Understanding the SKILL.md

Unlike the script-based approach, this SKILL.md doesn't contain a Run: command pointing to a script. Instead, it provides:

SectionPurpose
GoalTells the agent what outcome to produce
ConfigurationWhich env vars to read and what defaults to use
WorkflowNumbered steps with pointers to reference files
Important notesNon-obvious context that prevents common mistakes

The allowed-tools: Bash, Read setting lets the agent both read reference files and execute code. The agent writes its own script based on the workflow steps and reference materials.

Step 3: Add reference files

templates/slack-alert.md

Create .claude/skills/discount-audit/templates/slack-alert.md:

# Slack Alert Template
 
Use this Block Kit structure for the discount audit summary message.
 
## Block Kit JSON
 
```json
{
  "channel": "<SLACK_CHANNEL_ID>",
  "text": "<count> deal(s) need discount approval",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Discount Approval Needed"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*<count> deal(s)* have discounts above <threshold>% without approval:\n\n- <https://app.hubspot.com/contacts/<PORTAL_ID>/deal/<DEAL_ID>|Deal Name> — $amount at discount%\n- ..."
      }
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": "Review each deal and update the discount_approval property in HubSpot."
        }
      ]
    }
  ]
}
```
 
## Notes
 
- The top-level `text` field is required by the Slack API as a fallback for notifications and accessibility. Always include it.
- The HubSpot deal link format: `https://app.hubspot.com/contacts/<PORTAL_ID>/deal/<DEAL_ID>` (if portal ID is known) or `https://app.hubspot.com/contacts/deal/<DEAL_ID>`.
- List all unapproved deals in a single message rather than posting one message per deal.
- Include the deal name, amount, and discount percentage for each deal.

references/hubspot-deals-api.md

Create .claude/skills/discount-audit/references/hubspot-deals-api.md:

# HubSpot Deals API Reference
 
## Search for deals by custom property
 
Use the CRM Search API to find deals where a custom property exceeds a value.
 
**Request:**
 
```
POST https://api.hubapi.com/crm/v3/objects/deals/search
Authorization: Bearer <HUBSPOT_ACCESS_TOKEN>
Content-Type: application/json
```
 
**Body:**
 
```json
{
  "filterGroups": [
    {
      "filters": [
        {
          "propertyName": "discount_percent",
          "operator": "GT",
          "value": "<threshold>"
        }
      ]
    }
  ],
  "properties": [
    "dealname", "amount", "discount_percent",
    "discount_approval", "hubspot_owner_id", "dealstage"
  ],
  "limit": 100
}
```
 
- The `value` must be a string, even for number properties (e.g., `"15"` not `15`).
- `limit` max is 100. If there are more results, use the `after` cursor from `paging.next.after` in the response to paginate.
 
**Response shape:**
 
```json
{
  "total": 5,
  "results": [
    {
      "id": "12345",
      "properties": {
        "dealname": "Acme Corp Annual Contract",
        "amount": "72000",
        "discount_percent": "22",
        "discount_approval": "",
        "hubspot_owner_id": "67890",
        "dealstage": "contractsent"
      }
    }
  ],
  "paging": {
    "next": {
      "after": "100"
    }
  }
}
```
 
## Filtering notes
 
- HubSpot's search API has limited support for NOT_EQUAL filters on custom dropdown properties. To exclude "Approved" deals, fetch all high-discount deals and filter client-side.
- The `discount_approval` property returns an empty string when not set, not null.
- Custom property internal names are case-sensitive. Use exactly `discount_percent` and `discount_approval`.

Step 4: Test the skill

Invoke the skill conversationally:

/discount-audit

Claude will read the SKILL.md, check the reference files, write a script, run it, and report the results. A typical run looks like:

Searching for deals with discounts above 15%...
  Found 8 deals with high discounts
  Filtered to 5 unapproved deal(s)
  Posted summary to Slack
Done. Flagged 5 deal(s) needing discount approval.

What the Slack alert looks like

What you'll get
#deal-approvals
Deal Desk Botapp9:41 AM

Discount Approval Needed

5 deal(s) have discounts above 15% without approval:

- BrightPath Analytics Platform — $64,000 at 22% discount

- Contoso Platform Deal — $48,000 at 18% discount

- Widget Inc Expansion — $35,000 at 25% discount

Review each deal and update the discount_approval property in HubSpot.

Because the agent generates code on the fly, you can also make ad hoc requests:

  • "Which deals have discounts over 20%?" — the agent adjusts the threshold
  • "Show me unapproved discounts for the Enterprise pipeline only" — the agent adds a pipeline filter
  • "Post to #sales-leadership instead" — the agent changes the target channel
Test with a real deal

Create or update a deal in HubSpot with discount_percent set to 20 and leave discount_approval empty. Run the skill and verify a Slack message lists that deal. Then set discount_approval to "Approved" and run again — the deal should no longer appear.

Step 5: Schedule it (optional)

Option A: Cron + Claude CLI

# Run every Monday at 9am before deal desk meeting
0 9 * * 1 cd /path/to/your/project && claude -p "Run /discount-audit" --allowedTools 'Bash(*)' 'Read(*)'

Option B: GitHub Actions + Claude

name: Discount Audit
on:
  schedule:
    - cron: '0 14 * * 1'  # Every Monday at 9am ET
  workflow_dispatch: {}   # Manual trigger for testing
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          prompt: "Run /discount-audit"
          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 }}
          SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
GitHub Actions cron uses UTC

0 14 * * 1 runs at 2pm UTC, which is 9am ET. GitHub Actions cron may also have up to 15 minutes of delay. For time-sensitive alerting, use cron on your own server or a dedicated scheduler instead.

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 periodic audits — "flag all unapproved discounts before the weekly deal desk meeting" — rather than real-time approval flows
  • You want conversational flexibility — ad hoc queries like "which deals have discounts over 20%?" alongside scheduled checks
  • You're already using Claude Code and want skills that integrate with your workflow
  • You want to run tasks in the background via Claude Cowork while focusing on other work
  • You prefer guided references over rigid scripts — the agent adapts while staying reliable

When to switch approaches

  • You need interactive Approve/Deny buttons with automated HubSpot write-back → use n8n with Send and Wait or the code approach
  • You want a no-code setup with a visual builder → use Zapier or Make
  • You need real-time notifications the moment a discount is applied → use the code approach with webhooks
This skill audits current state, not real-time changes

This Claude Code skill searches for deals that currently have high unapproved discounts. It doesn't monitor for new discount changes in real time. If you need instant notifications when a rep applies a discount, use the n8n, Zapier, or code approach instead. The skill is best for periodic audits — "what needs approval right now?" — and ad hoc queries.

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 thresholds, filtered pipelines, summary grouped by rep, a different channel. 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 the script-based approach, the agent reads skill files and generates code each time. Typical cost is $0.01-0.05 per invocation depending on how many deals are returned and how much the agent needs to read. The HubSpot and Slack APIs themselves are free.

Can I change the discount threshold without editing files?

Yes. Just tell Claude: "Run /discount-audit with a 20% threshold." The SKILL.md specifies 15% as the default, but the agent can override it based on your request. No file changes needed.

Can I run this skill on a schedule without a server?

Yes. GitHub Actions (Option B in Step 5) runs Claude on a cron schedule using GitHub's infrastructure. The free tier includes 2,000 minutes/month — more than enough for a weekly audit.

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, no per-call cost
  • 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.