Build a Monthly Revenue Report → Stripe + HubSpot + Slack + Sheets Workflow with n8n
Every month, someone on your team burns two hours stitching together a revenue number. They export a CSV from Stripe, cross-reference deal stages in HubSpot, paste both into a spreadsheet, reconcile t
Every month, someone on your team burns two hours stitching together a revenue number. They export a CSV from Stripe, cross-reference deal stages in HubSpot, paste both into a spreadsheet, reconcile the mismatches, and finally drop a summary in Slack that nobody fully trusts. Then they do it again next month. This workflow kills that ritual. It runs on the 1st of every month, pulls hard numbers from Stripe and HubSpot, writes them to a Google Sheet, and posts a clean summary to Slack — with zero human touch. Here is exactly how to build it in n8n.
The problem: your revenue number lives in four places
Stripe knows what you actually collected. HubSpot knows what your pipeline promised and which deals closed. Google Sheets is where finance wants the history. Slack is where the team actually looks. None of these systems talk to each other by default, so the "monthly revenue report" becomes a manual reconciliation job.
The cost is not just the two hours. It is the staleness. By the time the report is compiled, it is already the 3rd or 4th, the number is a snapshot nobody re-checks, and discrepancies between Stripe cash and HubSpot bookings go unexplained. For a founder trying to read the business, a late and hand-assembled report is worse than no report — it invites false confidence. You need collected revenue, new MRR, refunds, and closed-won deal value side by side, generated the moment the month closes, before anyone has a chance to introduce a copy-paste error.
The solution: one scheduled n8n workflow
The entire report is a single n8n workflow with a predictable shape: a time trigger fires on the 1st, two parallel branches hit Stripe and HubSpot, a Code node merges and calculates the metrics, and two output nodes write to Sheets and Slack. Because n8n runs the API calls server-side and n8n handles pagination and auth, you get a report that is deterministic and auditable — the same logic every month, no forgotten steps.
The node chain looks like this:
- Schedule Trigger — fires 1st of month, 08:00
- Stripe node (or HTTP Request) — pull last month's charges and refunds
- HubSpot node — pull deals closed-won in the same window
- Merge node — combine both branches
- Code node — compute totals, MRR, refund rate, deltas
- Google Sheets node — append a row to the history tab
- Slack node — post the formatted summary
Step-by-step setup in n8n
1. The trigger. Add a Schedule Trigger node. Set the mode to "Cron" and use the expression 0 8 1 * * — 08:00 on the 1st of every month. Running a few hours into the day (not midnight) avoids catching Stripe's end-of-month settlement lag.
2. Compute the date window. Add a Code node immediately after the trigger. Because the report always covers the previous month, calculate the boundaries in code rather than hardcoding them:
const now = new Date();
const end = new Date(now.getFullYear(), now.getMonth(), 1);
const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
return [{ json: { start: Math.floor(start/1000), end: Math.floor(end/1000), label: start.toISOString().slice(0,7) } }];
The Unix timestamps feed Stripe; the label (e.g. 2026-06) becomes your Sheets row key.
3. Pull Stripe revenue. Add the Stripe node (resource: Charge, operation: Get All) or an HTTP Request node against https://api.stripe.com/v1/charges. Pass created[gte]={{ $json.start }} and created[lt]={{ $json.end }}. Enable "Return All" so n8n auto-paginates past the 100-record limit — this is the single most common cause of undercounted revenue. Add a second branch for the Refund resource so you can report net collected, not gross.
4. Pull HubSpot closed deals. Add the HubSpot node (resource: Deal, operation: Get All / Search). Filter on dealstage = closedwon and closedate within the same window. Return the amount property. This gives you bookings to sit next to Stripe's cash — the two rarely match exactly, and that gap is itself a signal worth reporting.
5. Merge and calculate. Use a Merge node (mode: Combine, or "Merge By Position") to bring both branches into one item, then a Code node to do the math. Sum Stripe charge amount (divide by 100 — Stripe returns cents), subtract refunds, count new subscriptions for MRR, and sum HubSpot deal amounts. Compute a month-over-month delta by reading the previous row from Sheets if you want trend arrows.
6. Append to Google Sheets. Add the Google Sheets node (operation: Append). Map columns: month, gross_stripe, refunds, net_collected, new_mrr, hubspot_bookings, deal_count. Appending (never overwriting) builds the historical series finance actually wants.
7. Post to Slack. Add the Slack node (operation: Send Message) targeting your #revenue channel. Use Block Kit or a simple formatted string with the key numbers bolded and a link to the Sheet. This is the artifact the team sees on the morning of the 1st.
Benefits: what changes after you ship this
Zero manual work. The report generates itself while everyone sleeps. The recurring two-hour task disappears permanently, and it never gets skipped during a busy month-end.
One source of truth. Stripe cash and HubSpot bookings appear in the same message, so the "why doesn't sales' number match finance's number?" conversation happens with data in front of everyone instead of accusations.
A history that compounds. Because every run appends to Sheets, after six months you have a clean revenue series you can chart, forecast against, or drop into a board deck — no back-filling required.
Trust. A number produced by the same deterministic workflow every month is more credible than one assembled by hand under deadline. When the logic lives in n8n, anyone can open the workflow and see exactly how the figure was derived.
Common pitfalls (and how to avoid them)
Forgetting pagination. Both Stripe and HubSpot cap responses at ~100 records. If you skip "Return All," you silently report only the first page and your revenue looks smaller than it is. Always enable auto-pagination and spot-check the count against your dashboards the first month.
Cents vs. dollars. Stripe returns amounts in the smallest currency unit. Divide by 100. A report showing $4,200,000 instead of $42,000 is the classic first-run bug.
Timezone drift on the date window. Compute your month boundaries in UTC and stay consistent between the Stripe timestamp filter and the HubSpot closedate filter, or you will double-count or drop deals that land on the 1st. Test by running the workflow manually against a known past month before trusting it.
Mixing currencies. If you charge in more than one currency, group Stripe charges by currency in the Code node rather than summing blindly — otherwise you add euros to dollars. Report each currency on its own line or convert with a fixed monthly rate.
Silent failures. Add an Error Trigger workflow (or an error branch that posts to Slack) so a failed API call pings you instead of quietly producing no report. A missing report on the 1st should be loud, not invisible.
Build it once, test it against last month, and you never think about the monthly revenue report again.
Ja construimos isso pra voce
Nao comece do zero. O Monthly Revenue Report → Stripe + HubSpot + Slack + Sheets e um workflow n8n pronto para instalar — conecta suas ferramentas em minutos, sem codigo.
Instalar por $119 →