Post a daily Slack leaderboard of rep activity from HubSpot using Make

medium complexityCost: $10-29/mo

Prerequisites

Prerequisites
  • Make account (any plan — the Free plan supports daily scheduling)
  • HubSpot connection configured in Make via OAuth
  • Slack connection configured in Make (Bot Token with chat:write scope)

Why Make?

Make is a good middle ground between Zapier's simplicity and n8n's power. The visual scenario builder makes the workflow easy to understand at a glance, and Make's credit-based pricing is more predictable than Zapier's per-task model. The Free plan (1,000 credits/month) can handle daily leaderboards if you don't need the Code module.

The catch: ranking reps by total activity across three engagement types really needs the Code module, which requires the Core plan at $29/mo. Without it, you'd need a complex combination of Iterator, Array Aggregator, and Text Aggregator modules. If you need code-based transformations and Make is your platform of choice, the Core plan pays for itself in time saved.

How it works

  • Scheduler triggers every weekday morning at your configured time
  • Set variable module computes yesterday's timestamp range in milliseconds
  • Three parallel HTTP modules search HubSpot for calls, emails, and meetings
  • HTTP module fetches the owner roster for name resolution
  • Code module (Core plan) aggregates activity per rep, ranks by total, and formats the leaderboard text
  • Slack module posts the formatted leaderboard to your channel

Step 1: Create a scenario and schedule it

Create a new scenario in Make. Click the clock icon to configure the schedule:

  • Schedule type: At regular intervals
  • Run scenario: Every day
  • Time: 08:00
  • Timezone: Select your team's timezone
Weekday filter

To skip weekends, add a Filter after the trigger with the condition: formatDate(now; "E") is not equal to 6 AND not equal to 7 (Saturday and Sunday in Make's date system).

Step 2: Set date variables

Add a Set variable module (under Tools) to calculate yesterday's date range:

  • Variable 1: yesterday_start = formatDate(addDays(now; -1); "YYYY-MM-DDT00:00:00.000Z")
  • Variable 2: yesterday_start_ms = Unix timestamp in milliseconds of yesterday's midnight

You'll use these in the HubSpot search filters.

Step 3: Fetch rep owners

Add an HTTP module (Make an API Call):

  • URL: https://api.hubapi.com/crm/v3/owners?limit=100
  • Method: GET
  • Headers: Use the HubSpot connection (Make adds Authorization automatically)

This returns owner IDs mapped to names.

Step 4: Fetch yesterday's calls, emails, and meetings

Add three HTTP modules in parallel (connect all three to the Set variable output):

Calls:

  • URL: https://api.hubapi.com/crm/v3/objects/calls/search
  • Method: POST
  • Body type: Raw JSON
{
  "filterGroups": [
    {
      "filters": [
        {
          "propertyName": "hs_timestamp",
          "operator": "GTE",
          "value": "{{yesterday_start_ms}}"
        },
        {
          "propertyName": "hs_timestamp",
          "operator": "LT",
          "value": "{{today_start_ms}}"
        }
      ]
    }
  ],
  "properties": ["hs_timestamp", "hubspot_owner_id"],
  "limit": 100
}

Emails: Same body, URL = https://api.hubapi.com/crm/v3/objects/emails/search

Meetings: Same body, URL = https://api.hubapi.com/crm/v3/objects/meetings/search

Timestamp format

HubSpot's hs_timestamp filter expects Unix milliseconds as a string. Use Make's parseDate() and date math functions to compute the values. If you pass seconds instead of milliseconds, the search silently returns zero results.

Step 5: Aggregate and rank with a Code module

Make's visual aggregators work well for simple sums, but ranking reps across three activity types is easier in code. Add a Tools -> Set variable module or Code module (available on Core plan+) to process all three responses:

If using the Code module:

const owners = JSON.parse(getVariable('owners_response')).results || [];
const calls = JSON.parse(getVariable('calls_response')).results || [];
const emails = JSON.parse(getVariable('emails_response')).results || [];
const meetings = JSON.parse(getVariable('meetings_response')).results || [];
 
const ownerMap = {};
for (const o of owners) {
  ownerMap[o.id] = `${o.firstName || ''} ${o.lastName || ''}`.trim() || o.email;
}
 
const reps = {};
function count(items, type) {
  for (const item of items) {
    const oid = item.properties.hubspot_owner_id;
    if (!oid) continue;
    if (!reps[oid]) reps[oid] = { calls: 0, emails: 0, meetings: 0, total: 0 };
    reps[oid][type]++;
    reps[oid].total++;
  }
}
 
count(calls, 'calls');
count(emails, 'emails');
count(meetings, 'meetings');
 
const medals = ['\u{1F947}', '\u{1F948}', '\u{1F949}'];
const ranked = Object.entries(reps)
  .map(([id, c]) => ({ name: ownerMap[id] || id, ...c }))
  .sort((a, b) => b.total - a.total);
 
return ranked.map((r, i) => {
  const medal = medals[i] || `${i + 1}.`;
  return `${medal} *${r.name}* — ${r.total} activities (${r.calls}C ${r.emails}E ${r.meetings}M)`;
}).join('\n');

If you're on the Free plan without the Code module, you can use a combination of Array Aggregator, Iterator, and Text Aggregator modules, but the logic is significantly more complex for a ranking use case.

Step 6: Send to Slack

Add a Slack module -> Create a Message:

  • Channel: Select #sales-activity
  • Text:
:trophy: *Rep Activity Leaderboard*
Activity for {{formatDate(addDays(now; -1); "dddd, MMM D")}}
 
{{leaderboard_text}}
 
_C = Calls | E = Emails | M = Meetings_

For richer formatting, use Slack -> Make an API Call:

  • URL: /chat.postMessage
  • Method: POST
  • Body: JSON with channel, text (fallback), and blocks array

Step 7: Add error handling and activate

  1. On each HTTP module: add a Resume error handler with a 10-second delay (handles 429 rate limits)
  2. Enable email notifications for failed executions in Scenario settings -> Error handling
  3. Click Run once to test
  4. Toggle the scenario to Active

Troubleshooting

Common questions

Does the leaderboard work on the Free plan?

Partially. The Free plan (1,000 credits/month) supports the HTTP modules and Slack module, but not the Code module needed for ranking. You can replicate the ranking with Iterator + Array Aggregator modules, but the setup is significantly more complex. The Core plan ($29/mo) is recommended.

How many Make credits does this use per month?

Each run uses 6-8 credits (trigger + variable + owners + 3 searches + code + Slack). At 20 weekday runs, that's 120-160 credits/month — well within the Free plan's 1,000 credits, or about 1.5% of the Core plan's 10,000 credits.

Can I run the three activity searches in parallel to save time?

Yes. Connect all three HTTP modules to the same Set variable output. Make executes sibling branches concurrently, cutting API call time by roughly two-thirds. Watch out for HubSpot's 5 req/sec Search API limit — with three parallel requests, you're using 3 of 5 allowed per second.

Cost and credits

  • Free plan: 1,000 credits/month. This scenario uses approximately 6-8 credits per run (1 trigger + 1 variable + 1 owners + 3 searches + 1 code/aggregation + 1 Slack). At 20 weekday runs/month, that's ~120-160 credits/month.
  • Core plan: $29/month for 10,000 credits. Required if you use the Code module.

Next steps

  • Use Make's Data Store to persist daily leaderboards and add a weekly summary every Friday
  • Add a Router to post separate leaderboards for SDRs and AEs based on owner team mapping
  • Weighted scoring — multiply meetings by 3, calls by 2, and emails by 1 in the ranking calculation

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.