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

medium complexityCost: $20-50/mo

Prerequisites

Prerequisites
  • Zapier Professional plan (required for Schedule trigger + Webhooks + Code steps)
  • HubSpot private app token with crm.objects.deals.read scope
  • Slack workspace connected to Zapier
  • A mapping of HubSpot owner IDs to Slack user IDs

Why Zapier?

Zapier works for missing field alerts if your team already uses it. The main trade-off is that Code by Zapier can only return flat key-value pairs, so encoding per-owner deal groups requires a delimiter-based workaround. For teams with few owners (3-5 reps), this is manageable. For larger teams, the n8n or code approach is cleaner.

How it works

  • Schedule by Zapier fires daily at 7 AM
  • Webhooks by Zapier searches for deals missing close date and amount via two API calls
  • Code by Zapier deduplicates, groups by owner, and encodes the results as delimited strings
  • Filter skips the run if no deals have missing fields
  • Looping by Zapier iterates over owner groups and sends Slack DMs

Step 1: Schedule a daily trigger

Create a new Zap with Schedule by Zapier:

  • Frequency: Every Day
  • Time: 7:00 AM

Step 2: Search for deals missing close date

Add a Webhooks by Zapier -> Custom Request step:

  • Method: POST
  • URL: https://api.hubapi.com/crm/v3/objects/deals/search
  • Headers: Authorization: Bearer YOUR_TOKEN, Content-Type: application/json
  • Data:
{
  "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 another Webhooks by Zapier -> Custom Request step with the same structure, swapping 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 requests?

HubSpot's Search API uses AND logic within a filter group. You cannot OR two NOT_HAS_PROPERTY filters together. Two separate requests merged in the next step is the cleanest approach.

Step 4: Merge and group with Code by Zapier

Add a Code by Zapier step (JavaScript). Pass both webhook responses as input data:

const closeResp = JSON.parse(inputData.missingCloseRaw);
const amountResp = JSON.parse(inputData.missingAmountRaw);
 
const seen = new Set();
const allDeals = [];
 
for (const deal of [...(closeResp.results || []), ...(amountResp.results || [])]) {
  if (seen.has(deal.id)) continue;
  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,
    ownerId: props.hubspot_owner_id || "unassigned",
    missing: missing.join(", "),
  });
}
 
if (allDeals.length === 0) {
  return { count: 0, message: "" };
}
 
// Group by owner
const byOwner = {};
for (const deal of allDeals) {
  if (!byOwner[deal.ownerId]) byOwner[deal.ownerId] = [];
  byOwner[deal.ownerId].push(deal);
}
 
// Format one message per owner
const ownerMessages = Object.entries(byOwner).map(([ownerId, deals]) => {
  const lines = deals.map(d =>
    `- <https://app.hubspot.com/contacts/YOUR_PORTAL_ID/deal/${d.id}|${d.name}> -- missing *${d.missing}*`
  ).join("\n");
  return `${ownerId}:::${deals.length}:::${lines}`;
});
 
return { count: allDeals.length, owners: ownerMessages.join("|||") };
Zapier Code output limits

Code by Zapier can only return simple key-value pairs, not arrays. The script above encodes owner groups as a delimited string. Parse it in a downstream Looping step or use multiple Slack steps.

Step 5: Filter empty results

Add a Filter step:

  • Only continue if: count is greater than 0

Step 6: Send Slack messages per owner

Add a Looping by Zapier step to iterate over the |||-separated owner messages, then inside the loop add a Slack -> Send Direct Message step:

  • User: Map the HubSpot owner ID to the Slack user ID
  • Message:
*Missing Deal Fields*
You have deals with incomplete data:
 
{{lines from code output}}
 
Please update these deals today so forecasting stays accurate.

If owner-to-Slack mapping is too complex for your setup, replace the loop with a single Slack -> Send Channel Message step posting the full list to #sales-pipeline.

Step 7: Test and publish

  1. Test each step individually
  2. Verify the Slack DM formatting and links
  3. Turn the Zap on

Troubleshooting

Common questions

How many Zapier tasks does this use per month?

About 150-180 tasks/month. Each daily run uses 5-6 base tasks (schedule + 2 webhooks + code + filter + Slack). If you loop over multiple owners, add ~1 task per owner per day. On the Professional plan (750 tasks/month), this leaves room for other Zaps.

Can I send a single channel message instead of per-owner DMs?

Yes. Replace the Looping + DM steps with a single Slack channel message step. Combine all owners' deals into one formatted message in the Code step. This reduces task usage and simplifies the Zap.

Why can't I use Zapier's built-in HubSpot actions?

Zapier's native HubSpot integration doesn't support the NOT_HAS_PROPERTY filter operator. You need Webhooks by Zapier to call the Search API directly with the full filter syntax.

Cost

  • Professional plan: $29.99/mo. Uses ~5-6 tasks per daily run (schedule + 2 webhooks + code + filter + Slack) = ~180 tasks/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.