Auto-archive stale HubSpot deals using Make

medium complexityCost: $10-29/mo

Prerequisites

Prerequisites
  • Make account (Core plan recommended for multiple scenarios)
  • HubSpot connection in Make via OAuth (needs crm.objects.deals.read and crm.objects.deals.write scopes)
  • Slack connection in Make
  • A Google Sheet or Make Data Store to track warned deals

Overview

Make doesn't have a built-in "wait 48 hours" equivalent like n8n's Wait node. Instead, you'll use two separate scenarios:

  1. Scenario 1 (daily): Finds stale deals, warns owners in Slack, records the warned deals and timestamp in a Data Store
  2. Scenario 2 (daily, offset by a few hours): Checks the Data Store for deals warned 48+ hours ago, verifies they're still stale, and closes them

This two-scenario pattern is the standard Make approach for delayed actions.

Scenario 1: Find and warn

Step 1: Create the warning scenario and schedule it

Create a new scenario. Set the schedule:

  • Schedule type: Every day
  • Time: 07:00
  • Timezone: Your team's timezone

Step 2: Search for stale deals

Add an HTTP module (Make an API Call) with the HubSpot connection:

  • URL: https://api.hubapi.com/crm/v3/objects/deals/search
  • Method: POST
  • Body:
{
  "filterGroups": [
    {
      "filters": [
        {
          "propertyName": "hs_lastmodifieddate",
          "operator": "LT",
          "value": "{{formatDate(addDays(now; -60); 'X')}}000"
        },
        {
          "propertyName": "dealstage",
          "operator": "NOT_IN",
          "values": ["closedwon", "closedlost"]
        }
      ]
    }
  ],
  "properties": [
    "dealname", "amount", "dealstage", "hubspot_owner_id",
    "hs_lastmodifieddate"
  ],
  "sorts": [
    { "propertyName": "hs_lastmodifieddate", "direction": "ASCENDING" }
  ],
  "limit": 100
}
Timestamp in Make

formatDate(addDays(now; -60); "X") returns a Unix timestamp in seconds. Append 000 to convert to milliseconds for HubSpot's filter.

Step 3: Iterate over results

Add an Iterator module to loop through the results array from the search response. Each deal becomes a separate bundle.

Step 4: Check the Data Store for duplicates

Before warning, check if you've already warned about this deal (to avoid duplicate Slack messages on consecutive runs).

First, create a Data Store called "Stale Deal Warnings" with these fields:

FieldType
deal_idText (key)
deal_nameText
warned_atDate
owner_idText

Add a Data Store → Search Records module:

  • Data store: Stale Deal Warnings
  • Filter: deal_id equals {{id}} (from the Iterator)

Add a Filter module after the search:

  • Condition: Number of records equals 0 (only proceed for deals not yet warned)

Step 5: Warn in Slack

Add a Slack → Create a Message module:

  • Channel: #sales-pipeline
  • Text:
:warning: *Stale Deal Warning*
 
*{{dealname}}* has had no activity for {{round(dateDifference(now; hs_lastmodifieddate; "d"))}} days.
 
This deal will be moved to Closed Lost in 48 hours unless someone updates it.
 
<https://app.hubspot.com/contacts/YOUR_PORTAL_ID/deal/{{id}}|View deal in HubSpot>

Step 6: Record the warning

Add a Data Store → Add/Replace a Record module:

  • Data store: Stale Deal Warnings
  • Key: {{id}} (the deal ID)
  • deal_id: {{id}}
  • deal_name: {{dealname}}
  • warned_at: {{now}}
  • owner_id: {{hubspot_owner_id}}

Step 7: Activate Scenario 1

Test with Run once, then toggle to Active.


Scenario 2: Close expired warnings

Step 1: Create the closing scenario

Create a second scenario. Set the schedule:

  • Schedule type: Every day
  • Time: 09:00 (runs 2 hours after the warning scenario, but the 48-hour check ensures timing)

Step 2: Search the Data Store for expired warnings

Add a Data Store → Search Records module:

  • Data store: Stale Deal Warnings
  • Filter: warned_at less than {{addHours(now; -48)}}

This returns all deals that were warned more than 48 hours ago.

Step 3: Re-check each deal in HubSpot

Add an HTTP module to fetch the deal's current state:

  • URL: https://api.hubapi.com/crm/v3/objects/deals/{{deal_id}}
  • Method: GET
  • Query string: properties=hs_lastmodifieddate,dealstage

Step 4: Filter out deals that were updated

Add a Filter module:

  • Condition: hs_lastmodifieddate is before {{addHours(now; -48)}} AND dealstage is not closedwon or closedlost

Deals that were updated during the grace period (or already closed manually) skip the close step.

Step 5: Close the deal in HubSpot

Add an HTTP module:

  • URL: https://api.hubapi.com/crm/v3/objects/deals/{{deal_id}}
  • Method: PATCH
  • Body:
{
  "properties": {
    "dealstage": "closedlost",
    "closed_lost_reason": "Stale — auto-archived after 60 days"
  }
}

Step 6: Notify Slack

Add a Slack → Create a Message module:

  • Text:
:file_cabinet: *Deal Auto-Archived*
 
*{{deal_name}}* was moved to Closed Lost after 60+ days of inactivity. No objection was received during the 48-hour grace period.

Step 7: Clean up the Data Store

Add a Data Store → Delete a Record module to remove the warning entry:

  • Key: {{deal_id}}

This keeps the Data Store clean — only active warnings remain.

Data Store cleanup for saved deals

Deals that were updated during the grace period also need to be removed from the Data Store. Add a second branch after the Filter in Step 4 that catches filtered-out deals (those that were updated) and deletes their Data Store records too. Otherwise, they'll be re-checked every day forever.

Step 8: Activate Scenario 2

Test with Run once, then toggle to Active.

Cost

  • Free plan: Scenario 1 uses ~3-5 credits per stale deal (iterator + data store check + Slack + data store write). Scenario 2 uses ~4-5 credits per expired warning. With 10 stale deals per week, expect ~80-100 credits/week (~400/month). Fits the free plan's 1,000 credits.
  • Core plan: $29/month for 10,000 credits. Needed if you have high deal volume or add more complex logic.

Need help implementing this?

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