Instrument x Seth Akkerman
Projects Automated HubSpot Pipeline Reports

Automated HubSpot Pipeline Reports

A GitHub Actions-powered automation that pulls live deal data from HubSpot every weekday and generates a formatted Slack canvas — surfacing wins, active pitches, and kickoffs without anyone having to manually post a thing.

Sales pipeline visibility is one of those things that’s genuinely valuable to a team but consistently falls through the cracks — because it depends on someone remembering to post an update every week. This tool removes that dependency entirely. It’s a scheduled automation that pulls live deal data from HubSpot and generates a formatted Slack canvas every weekday at 10am, complete with wins, active pitches broken out by close probability, and upcoming project kickoffs.

How it works

A GitHub Actions workflow fires on a cron schedule (Monday–Friday, 10am PST) and kicks off a Node.js script that does two things: first, it queries the HubSpot CRM API for deals filtered by team, stage, and date window — mapping owner IDs to real names via the Owners API. Second, it calls the Slack Canvas API to either create or update a canvas in the target channel, then posts a summary message with key stats and a direct link to the full report. The canvas is structured markdown: a won-deals table, active pitches grouped by pipeline stage (Closing 90%, High Likelihood 65%, Moderate 30%, Qualified Lead 15%), and an optional kickoffs section.

The build

The system was built to support multiple studio teams at Instrument — Brand Studio and Product Studio each have their own canvas, channel, and team filter. Switching teams is a one-line config change. The canvas update flow uses canvases.edit with a replace operation so the canvas URL never changes and channel history stays clean. Owner fetching uses a direct fetch() call rather than the HubSpot SDK, which had reliability issues on that particular endpoint. Environment variables are managed via GitHub Secrets in CI and a local .env for development — no infrastructure required beyond a GitHub repo.

Why it matters

The real value here isn’t the technology — it’s the cognitive load it removes. Before this, someone on the team was manually compiling and posting pipeline updates every week. That’s a task that’s easy to skip, easy to do inconsistently, and easy to resent. Automating it means the team gets reliable, timely visibility into what’s winning and what’s in flight, and the person who was doing it gets that time back. The same pattern applies broadly: any CRM-to-communication-channel sync — agency new business, product sales, recruiting pipelines — can be automated this way with minimal infrastructure and zero ongoing maintenance cost.

Applying this to your project

  1. Copy the whole hubspot-slack-canvas/ folder and drop node_modules, .env (it holds real tokens — never commit it), and .git if you’re starting a fresh repo. Keep src/, scripts/, .github/workflows/, template.yaml, package.json, and package-lock.json.
  2. Create a HubSpot Private App (scope crm.objects.deals.read) and a Slack App with Bot Token Scopes canvases:write, canvases:read, chat:write, channels:read. Put the resulting values in a local .env (copy .env.example first) as HUBSPOT_ACCESS_TOKEN, SLACK_BOT_TOKEN, SLACK_CHANNEL_ID, and TEAM_NAME. For automated runs, add the same values as GitHub Actions repo secrets (Settings → Secrets and variables → Actions) — the workflow at .github/workflows/weekly-report.yml reads them by exact name.
  3. Swap out the hardcoded business logic in src/hubspot.js: the STAGES object maps this org’s actual HubSpot pipeline-stage IDs to labels, and the instrument_team property name is this company’s custom field for team filtering — replace it with whatever custom property (or drop the filter) matches your own HubSpot portal. Also update the stageOrder array and emoji labels in src/slack.js to match your renamed stages.
  4. The integration point for new data is formatDeal() in src/hubspot.js (add fields to DEAL_PROPERTIES and the returned object) and the corresponding markdown table columns in formatCanvasMarkdown() in src/slack.js — that’s where a designer would add or remove columns shown in the Slack Canvas.
  5. Gotcha: the “Project Kickoffs” section is fully built but intentionally commented out in both src/hubspot.js (fetchKickoffs) and src/slack.js (the markdown block and summary stat) — search for “KICKOFFS TEMPORARILY DISABLED” to re-enable it. Also note scripts/run-ci.js expects env vars to already be injected by CI (no dotenv), while scripts/test-local.js is the one that reads .env — don’t confuse the two when testing locally.

Other ways to repurpose this

Product Design

Web Design

Marketing

Branding

Dependencies

@hubspot/api-client 11.2.0 Official HubSpot SDK used to run CRM search queries against the Deals API, filtering by pipeline stage, close date, and a custom "instrument_team" property to build the weekly won/pitching deal lists.
@slack/web-api 7.13.0 Slack's Bot SDK used to create a channel Canvas from generated markdown and post a companion summary message with deal counts and a link to the full canvas.
dotenv 16.6.1 Loads HubSpot/Slack credentials from a local .env file for local test runs only; the GitHub Actions and AWS Lambda code paths read process.env directly without it.
Role
Design Engineer
Tools
Node.js GitHub Actions HubSpot API Slack API Slack Canvas API
Video thumbnail for Automated HubSpot Pipeline Reports