# How to post a daily HubSpot pipeline report to Slack automatically

> Four ways to get a HubSpot pipeline report into Slack every morning: HubSpot's recurring Share to Slack, Zapier or Make, your own script, or a Claude-built workflow.

- Published: 2026-10-25
- URL: https://teton.so/guides/post-daily-hubspot-pipeline-report-to-slack

The quickest way to post a daily HubSpot pipeline report to Slack is HubSpot's own recurring Share to Slack. Open a dashboard or report, choose **Actions > Share to Slack**, make it recurring, pick a channel and set it to Daily. It posts a preview and a link, not a custom summary. If you want the numbers in the message, per-stage totals, or a line on what moved since yesterday, use Zapier or Make, a short script against the HubSpot API, or a workflow that Claude writes and a platform runs on a schedule.

Below are the options, what each one actually posts, and when each fits.

## 1. HubSpot's recurring Share to Slack (no code)

HubSpot lets you [share reports and dashboards to Slack once or on a recurring basis](https://knowledge.hubspot.com/dashboard/email-or-export-reports-and-dashboards). Older community threads and some third-party guides say it can't. That was true at one point. HubSpot staff [put recurring Slack shares on the roadmap](https://community.hubspot.com/t5/HubSpot-Ideas/Share-Dashboard-via-Slack/idi-p/364835) and later said they had prioritized them.

Setup:

1. Go to **Reporting > Dashboards** and open the dashboard with your pipeline reports.
2. In the upper right, click **Actions**, then **Share to Slack**.
3. Choose whether to share with a channel or a user, and pick the channel.
4. Make it recurring and give it a nickname. Then set the frequency (Daily, Weekly or Monthly) and the time of day.

HubSpot's help page reuses wording from the email flow here ("Is this a recurring email?"). The label you see may differ. [Clearfeed's walkthrough](https://clearfeed.ai/how-to/share-hubspot-reports-through-slack) shows it as "Yes, this is a recurring slack message." To edit it later, open the report's **More** menu and choose **Manage recurring shares**. Only a Super Admin or the dashboard owner can change or delete a recurring message.

The HubSpot Slack app must be connected first. That usually takes a HubSpot super admin (or someone with App Marketplace permissions) and a Slack admin.

**What gets posted:** a preview of the report and a link. People who have access to the report in HubSpot can click through to it. You can't change the format, add a "what moved since yesterday" line, or split it by rep.

**Good for:** teams that already have the right dashboard and just want a daily nudge to look at it.

**Check before relying on it:** HubSpot doesn't clearly say which plans include recurring shares. Also check who owns the share, since it's tied to a user. If a channel is missing from the picker, it is often a private channel the Slack app hasn't been added to.

## 2. HubSpot workflows: alerts, not a digest

Many people try **Automation > Workflows** first, using the **Send a Slack notification** action under Integrated apps. It [requires a Professional or Enterprise subscription](https://knowledge.hubspot.com/integrations/manage-your-notifications-in-slack). It can post messages with buttons to create tasks or update properties.

Each notification is triggered by one record, so a workflow can tell you that a deal moved to Closed Won. It can't send you one summary of the whole pipeline each morning. Use it alongside a daily report, not instead of one.

You may also see an older workaround: email a recurring report to a Slack channel's email address. It predates native recurring shares and isn't worth setting up now.

## 3. Zapier, Make or a similar tool

An automation tool lets you control the message. A typical [Zapier template](https://zapier.com/automations/sales/sales-reporting/pipeline-reporting/send-daily-alert-for-deals-expiring-tomorrow-to-team) chains these steps:

1. A daily schedule trigger
2. A HubSpot deal search
3. A filter that stops if nothing matched
4. A formatter that turns the results into line items
5. A loop that fetches each deal
6. A Slack message

Tools like Bardeen add a model step that summarizes the deals before posting.

**Good for:** a formatted list (deals closing this week, deals over a threshold) without writing code.

**The catch:** per-stage totals and "compared with yesterday" are awkward to build. Every search, loop iteration and fetch counts against your task quota. Nothing in the chain remembers yesterday's numbers unless you add storage, such as a Google Sheet.

## 4. Your own script: HubSpot API + Slack webhook + cron

A short script gives you full control. You need:

- A HubSpot private app token with [read scope on deals](https://kestra.io/blueprints/hubspot-stalled-deal-pipeline-sentinel) (`crm.objects.deals.read`). Don't use old tutorials that pass an `api_key` to `v1` endpoints, because HubSpot replaced API keys with private app tokens.
- A Slack incoming webhook URL for the channel.

The script below totals open value per stage and lists deals edited in the last 24 hours. Save it as `pipeline_report.py`; it needs the `requests` library (`pip install requests`).

```python
import os, datetime, requests

TOKEN = os.environ["HUBSPOT_TOKEN"]
WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
PIPELINE = os.environ.get("HUBSPOT_PIPELINE", "default")
API = "https://api.hubapi.com"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

def stages():
    r = requests.get(f"{API}/crm/v3/pipelines/deals/{PIPELINE}", headers=H, timeout=30)
    r.raise_for_status()
    return sorted(r.json()["stages"], key=lambda s: s["displayOrder"])

def deals():
    body = {
        "filterGroups": [{"filters": [
            {"propertyName": "pipeline", "operator": "EQ", "value": PIPELINE}]}],
        "properties": ["dealname", "amount", "dealstage", "hs_lastmodifieddate"],
        "limit": 100,
    }
    while True:
        r = requests.post(f"{API}/crm/v3/objects/deals/search", headers=H, json=body, timeout=30)
        r.raise_for_status()
        data = r.json()
        yield from data["results"]
        after = data.get("paging", {}).get("next", {}).get("after")
        if not after:
            break
        body["after"] = after

stage_list = stages()
label = {s["id"]: s["label"] for s in stage_list}
totals = {s["id"]: [0, 0.0] for s in stage_list}
since = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=24)
recent = []

for d in deals():
    p = d["properties"]
    amount = float(p.get("amount") or 0)
    if p["dealstage"] in totals:
        totals[p["dealstage"]][0] += 1
        totals[p["dealstage"]][1] += amount
    modified = datetime.datetime.fromisoformat(p["hs_lastmodifieddate"].replace("Z", "+00:00"))
    if modified >= since:
        recent.append(f"- {p['dealname']} ({label.get(p['dealstage'], '?')}, ${amount:,.0f})")

lines = [f"*{label[s]}*: {n} deals, ${v:,.0f}" for s, (n, v) in totals.items()]
text = "*Pipeline this morning*\n" + "\n".join(lines)
if recent:
    text += "\n\n*Edited in the last 24h*\n" + "\n".join(recent[:15])

r = requests.post(WEBHOOK, json={"text": "Pipeline report",
    "blocks": [{"type": "section", "text": {"type": "mrkdwn", "text": text[:2900]}}]}, timeout=30)
r.raise_for_status()
```

Run it on weekdays at 8:00 with cron. Cron uses the server's time zone unless your cron supports `CRON_TZ`:

```bash
0 8 * * 1-5 cd ~/reports && HUBSPOT_TOKEN=... SLACK_WEBHOOK_URL=... python3 pipeline_report.py >> report.log 2>&1
```

In practice, load the token and webhook from an env file or secret store rather than writing them into the crontab.

**Two honest limits.** First, `hs_lastmodifieddate` changes on any edit, so "edited" is not "moved stage." To report real stage changes and changes in value, save each morning's deals to a table and diff against yesterday's. Second, the search endpoint pages results and has its own rate limits. Check the current limits on developers.hubspot.com before you point the script at a large portal.

**Good for:** teams with a server and someone happy to own it. You also own token rotation, failures that stop the post without warning, and the storage for snapshots.

## 5. Have Claude build it once, then run it in the cloud

You can also ask Claude Code in plain language for the report you want and iterate until the Slack message is right. Then make that job permanent instead of maintaining the script yourself.

[Teton](https://teton.so) works this way. After you get the output right with Claude, type `/teton`. Claude writes the job up as one workflow file: a cron trigger with a timezone, the steps, and the HubSpot and Slack credentials it needs. One deploy makes it live, and it runs whether your laptop is on or not. The fetching and formatting run the same way every time. Claude is only called for steps that need judgment, such as a two-line summary of what changed.

Some details matter for this particular report:

- Each workspace has its own Postgres database. The job can store today's deal snapshot and compare it with yesterday's, which gives you true "what moved since yesterday" without setting up storage.
- Shared context files can hold your team's definitions, such as what counts as a stalled deal or which stages matter. Editing one changes the next run without a deploy.
- You enter credentials once in the dashboard, not in the chat or the file. They are only sent to the API hosts the workflow declares.
- Every run is listed with its result and cost. Every version is kept, so rolling back to the last good report is one click.

**Good for:** a custom report with day-over-day changes that the team relies on and that nobody wants to maintain on a server.

## How to choose

| | Setup effort | Numbers in the message | What moved since yesterday | Runs where |
| --- | --- | --- | --- | --- |
| HubSpot Share to Slack | Minutes | No, preview and link | No | HubSpot |
| HubSpot workflows | Minutes | Per deal only | No, one alert per record | HubSpot |
| Zapier / Make | An hour or so | Yes | Only with added storage | Vendor cloud |
| Your own script | A few hours | Yes, any format | If you add a database | Your server |
| Teton | Do it once with Claude | Yes, any format | Yes, Postgres per workspace | Cloud |

If the dashboard already shows what your team needs, use HubSpot's recurring share and stop there. Use one of the other options once people want the numbers and the changes in Slack itself.

## FAQ

### Can a HubSpot workflow send one daily pipeline summary?

No. Workflow Slack notifications fire per record, so you get one message per deal. For a daily summary, use a recurring dashboard share or a scheduled job that queries all deals at once.

### Why does HubSpot's Slack report show only a link?

Recurring Share to Slack posts a preview with a link back to HubSpot. The full numbers live in HubSpot, and viewers need access to the report to open it. If you need totals in the message, build the message yourself through the API.

### How do I show only deals that changed stage since yesterday?

Save each day's deal ID, stage and amount, then compare today's pull with yesterday's. `hs_lastmodifieddate` alone catches any edit, not just stage moves.

### How do I skip weekends and get the time zone right?

Use a cron expression like `0 8 * * 1-5` and make sure the scheduler runs it in your time zone. Plain cron uses the server's zone and GitHub Actions uses UTC. Teton's schedules take a timezone directly.

---

- [All guides](https://teton.so/guides)
- [Teton](https://teton.so): the cloud for Claude Code. Build a workflow with Claude once and Teton runs it on a schedule or a webhook.
