Flag HubSpot deals with missing fields and Slack the rep using code

medium complexityCost: $0

Prerequisites

Prerequisites
  • Node.js 18+ or Python 3.9+
  • HubSpot private app token with crm.objects.deals.read scope
  • Slack Bot Token (xoxb-...) with chat:write scope
  • A mapping of HubSpot owner IDs to Slack user IDs
  • Cron, GitHub Actions, or a cloud function for scheduling

Step 1: Set up the project

# Verify your HubSpot token works
curl -s -X POST "https://api.hubapi.com/crm/v3/objects/deals/search" \
  -H "Authorization: Bearer $HUBSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filterGroups": [{
      "filters": [{
        "propertyName": "closedate",
        "operator": "NOT_HAS_PROPERTY"
      }, {
        "propertyName": "dealstage",
        "operator": "NOT_IN",
        "values": ["closedwon", "closedlost"]
      }]
    }],
    "properties": ["dealname", "amount", "closedate", "hubspot_owner_id"],
    "limit": 5
  }' | python3 -m json.tool

Step 2: Search for deals with missing fields

Use the HubSpot Search API with NOT_HAS_PROPERTY to find deals where closedate or amount is null. Two separate searches are needed because HubSpot applies AND logic within a filter group.

# Search for deals missing close date
curl -s -X POST "https://api.hubapi.com/crm/v3/objects/deals/search" \
  -H "Authorization: Bearer $HUBSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filterGroups": [{
      "filters": [
        {"propertyName": "closedate", "operator": "NOT_HAS_PROPERTY"},
        {"propertyName": "dealstage", "operator": "NOT_IN", "values": ["closedwon", "closedlost"]}
      ]
    }],
    "properties": ["dealname", "amount", "closedate", "dealstage", "hubspot_owner_id"],
    "limit": 100
  }'
 
# Search for deals missing amount
curl -s -X POST "https://api.hubapi.com/crm/v3/objects/deals/search" \
  -H "Authorization: Bearer $HUBSPOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filterGroups": [{
      "filters": [
        {"propertyName": "amount", "operator": "NOT_HAS_PROPERTY"},
        {"propertyName": "dealstage", "operator": "NOT_IN", "values": ["closedwon", "closedlost"]}
      ]
    }],
    "properties": ["dealname", "amount", "closedate", "dealstage", "hubspot_owner_id"],
    "limit": 100
  }'
NOT_HAS_PROPERTY vs empty string

NOT_HAS_PROPERTY matches deals where the property has never been set. If a rep sets a close date and then clears it, HubSpot may store it as an empty string rather than null. To catch both, add a second filter group with EQ operator and value: "" for each field.

Pagination

The search endpoint returns max 100 results per page. If you have more than 100 deals with missing fields, implement pagination using the after cursor from paging.next.after in the response. The endpoint caps at 10,000 total results.

Step 3: Schedule

Cron (server-based):

# Daily at 7 AM
0 7 * * * cd /path/to/missing-fields && python missing_fields.py

GitHub Actions (serverless):

name: Missing Deal Fields Alert
on:
  schedule:
    - cron: '0 12 * * *'  # 7 AM ET = 12 PM UTC
  workflow_dispatch: {}
jobs:
  alert:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install requests slack_sdk && python missing_fields.py
        env:
          HUBSPOT_TOKEN: ${{ secrets.HUBSPOT_TOKEN }}
          HUBSPOT_PORTAL_ID: ${{ secrets.HUBSPOT_PORTAL_ID }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
Environment variables

Never commit tokens to your repo. Use GitHub Secrets, .env files (gitignored), or your hosting platform's secrets manager.

Cost

  • Free -- GitHub Actions provides 2,000 minutes/month on the free tier. This script runs in under 10 seconds.

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.