Post a daily Slack leaderboard of rep activity from HubSpot using Make
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:writescope)
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
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
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), andblocksarray
Step 7: Add error handling and activate
- On each HTTP module: add a Resume error handler with a 10-second delay (handles 429 rate limits)
- Enable email notifications for failed executions in Scenario settings -> Error handling
- Click Run once to test
- Toggle the scenario to Active
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
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.