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

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • HubSpot private app token with crm.objects.deals.read scope
  • Slack app with Bot Token (chat:write scope)
  • n8n credentials for HubSpot and Slack
  • A mapping of HubSpot owner IDs to Slack user IDs (Google Sheet or static data)

Why n8n?

n8n is the best option for missing field alerts because the Code node handles deduplication, grouping by owner, and message formatting in a few lines of JavaScript. The visual workflow makes it easy to add more required fields or adjust the Slack message format. Self-hosted n8n runs this for free, and even cloud n8n uses just 30 executions per month for a daily check.

How it works

  • Schedule Trigger fires daily at 7 AM
  • HTTP Request nodes search for deals missing close date and amount separately (HubSpot requires separate queries)
  • Code node deduplicates results, identifies all missing fields per deal, and groups deals by owner
  • Slack node DMs each owner with their incomplete deals and direct HubSpot links

Step 1: Schedule a daily check

Add a Schedule Trigger node:

  • Trigger interval: Days
  • Trigger at hour: 7 (run before the team starts their day)

Step 2: Search for deals missing close date

Add an HTTP Request node named "Search Missing Close Date":

  • Method: POST
  • URL: https://api.hubapi.com/crm/v3/objects/deals/search
  • Authentication: HubSpot API credential
  • Body:
{
  "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
}

Step 3: Search for deals missing amount

Add a second HTTP Request node named "Search Missing Amount" with the same structure, but swap the filter:

{
  "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
}
Why two searches?

HubSpot's Search API applies AND logic within a filter group. You cannot OR two NOT_HAS_PROPERTY filters in a single group. Running two searches and merging results is the reliable approach.

Step 4: Merge and group by owner

Add a Code node to deduplicate and group deals by owner:

const missingClose = $('Search Missing Close Date').first().json.results || [];
const missingAmount = $('Search Missing Amount').first().json.results || [];
 
// Deduplicate by deal ID
const seen = new Set();
const allDeals = [];
for (const deal of [...missingClose, ...missingAmount]) {
  if (!seen.has(deal.id)) {
    seen.add(deal.id);
    const props = deal.properties;
    const missing = [];
    if (!props.closedate) missing.push('close date');
    if (!props.amount) missing.push('amount');
 
    allDeals.push({
      id: deal.id,
      name: props.dealname,
      stage: props.dealstage,
      ownerId: props.hubspot_owner_id || 'unassigned',
      missing,
    });
  }
}
 
const byOwner = {};
for (const deal of allDeals) {
  if (!byOwner[deal.ownerId]) byOwner[deal.ownerId] = [];
  byOwner[deal.ownerId].push(deal);
}
 
return Object.entries(byOwner).map(([ownerId, deals]) => ({
  json: { ownerId, deals, dealCount: deals.length }
}));

Step 5: Slack DM each rep

Add a Slack node (executes once per owner from the Code node output):

  • Operation: Send a message
  • Channel: DM the owner using their Slack user ID from your mapping
  • Message Type: Block Kit
{
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Missing Deal Fields"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "You have *{{ $json.dealCount }}* deals with missing fields:\n\n{{ $json.deals.map(d => `- <https://app.hubspot.com/contacts/YOUR_PORTAL_ID/deal/${d.id}|${d.name}> -- missing *${d.missing.join(', ')}*`).join('\\n') }}"
      }
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": "Please update these deals today so forecasting stays accurate."
        }
      ]
    }
  ]
}
Owner to Slack user mapping

To DM individual reps, you need a mapping of HubSpot owner IDs to Slack user IDs. Store this in a Google Sheet or as static data in the workflow using $getWorkflowStaticData('global'). Without this mapping, post to a shared channel like #sales-pipeline instead.

Step 6: Activate

  1. Replace YOUR_PORTAL_ID in the Slack block with your HubSpot portal ID
  2. Click Execute Workflow to test
  3. Verify Slack DMs contain the right deals and links
  4. Toggle to Active for daily checks
Skipping empty results

If no deals are missing fields, the Code node returns an empty array and the Slack node won't execute. No extra filter needed.

Troubleshooting

Common questions

Why does this need two separate HubSpot searches?

HubSpot's Search API applies AND logic within a filter group. You can't say "close date is missing OR amount is missing" in one query. Two searches merged in the Code node is the standard pattern.

How many n8n executions does this use per month?

30 executions (one per day). Each execution processes all deals in a single pass. On n8n Cloud Starter ($24/mo, 2,500 executions), this uses 1.2% of your quota.

Can I add more required fields beyond close date and amount?

Yes. Add another HTTP Request node for each field (e.g., next_step, hubspot_owner_id) and merge the results in the Code node. The deduplication logic handles any number of overlapping searches.

Cost

  • n8n Cloud: 1 execution/day = ~30/month. Well within the Starter plan's 2,500 executions.
  • Self-hosted: Free.

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.