# Month-end Stripe finance pack, stored and shared in Slack

> On the first business day of each month, we build a line-item CSV and written summary of last month's Stripe activity, file it, and post the numbers to Slack.

- Workflow type: agent
- Services: General Input Storage, Stripe, Google Sheets, Slack Bot
- Categories: Finance, Operations
- Published: 2026-08-31

## What it does

- Pulls last month's payments and invoices from Stripe and builds a line-item spreadsheet you could hand straight to your accountant
- Writes a short summary covering money collected, refunds, failed payments and the biggest invoices still unpaid, including what changed since the month before
- Files both files in your General Input Storage under a folder for that month, so the pack is still there next quarter when someone asks
- Adds one row per month to a Google Sheets ledger so the trend builds up, then posts the headline numbers to Slack with a download link

## What you'll need

- A Stripe account you can read payments and invoices from
- A General Input Storage store to keep the monthly packs in
- A Google Sheets spreadsheet to use as the running month-by-month ledger
- A Slack workspace and the channel where finance updates should land

## Prompt

On the first business day of every month at 7am, build a month-end finance pack for the month that just closed and park it in General Input Storage. Run this on a monthly cron and work out the period yourself: the pack covers the calendar month before the run date, from the first day at 00:00:00 to the last day at 23:59:59 in our reporting timezone. Convert those boundaries to Unix timestamps for the Stripe filters, and use the month in YYYY-MM form everywhere else.

Pull the period's billing activity from Stripe. Use List Charges with a created range over the period for everything that was attempted, successful and failed alike, and List Invoices with the same range for what was billed, including invoices that are still open or uncollectible at period end. Page through both with starting_after until has_more comes back false rather than stopping at the first page. Stripe amounts are in the smallest currency unit, so divide before displaying, and group totals by currency instead of adding different currencies together.

Write a line-item CSV with one row per transaction: date, Stripe id, customer name and email, description or invoice number, currency, gross amount, amount refunded, net amount, status, and for failures the decline code and message. Sort by date. Keep it plain CSV that opens cleanly in Excel or Sheets so it can go to an accountant without any cleanup.

Write a short summary in Markdown alongside it. It should state total collected, total refunded, the count and value of failed payments, and the largest invoices still outstanding at period end with the customer names against them. Then do the part that actually matters: compare the month against the one before it and say what changed. Read the prior month's row from the ledger with Get Values, and on the very first run pull the prior month from Stripe the same way you pulled this one. Call out real movements such as a jump in failed payments, an unusually large refund, an outstanding balance that grew, or revenue concentrating in a single customer, and say what most likely drove each one. If nothing moved materially, say so in a sentence rather than padding the memo. Do not simply restate the totals that are already in the CSV.

Upload both files to General Input Storage under a prefix for the month, such as finance/2026-07/line-items.csv and finance/2026-07/summary.md. Use Presign Upload for each file, then PUT the bytes to the URL it returns with the headers it gives back, sending text/csv and text/markdown as the content types and passing the real byte size. The store is durable and shared across the account, so the pack outlives this run and another workflow, an app, or a person in the dashboard can pick the same file up months later.

Append a one-row-per-month roll-up to the Google Sheets ledger with Append Values so the trend is visible over time. Columns: month, currency, gross collected, refunds, net collected, failed payment count, failed payment value, outstanding invoice value at period end, invoice count, and the storage keys for the two files. Store the storage keys, never a download URL, because download links expire within minutes while the key is permanent and can be re-presigned on demand by a later run or an app.

Finally, mint a fresh Presign Download link for the summary file and post to Slack with Send a Message in the finance channel. Lead with the headline numbers for collected, refunded, failed and outstanding, add the one or two lines of interpretation from the summary rather than the raw totals again, and include the link. Say plainly in the message that the download link is short lived and expires in about fifteen minutes, while the pack itself stays in the store under its prefix, so anyone who misses the window can ask for a new link instead of assuming the file is gone. Include the storage prefix in the message so it is obvious where the pack lives.

A few edge cases to handle. If the month had no billing activity at all, still write and file the pack and say so plainly instead of silently skipping. If files for that month already exist in the store, overwrite them and note in Slack that the pack was regenerated. If Stripe rate limits you while paging, back off and retry rather than shipping a partial month.

## How to customize

- Change when it runs, such as the third business day instead of the first, and which Slack channel gets the post
- Set what counts as worth flagging: how big a refund or how sharp a rise in failed payments earns a callout
- Adjust the folder naming and the ledger columns, for example adding processing fees, disputes or a per-currency breakdown

## FAQ

### Does the download link in the Slack message expire?

Yes. Download links are short lived, roughly fifteen minutes, and a fresh one is created each time the message is posted. The pack itself stays in your store, so if the link has gone stale you can simply have a new one generated instead of rebuilding the report.

### What happens on the very first run when there is no prior month to compare against?

The first pack pulls the previous month's figures from Stripe directly so you still get a comparison. From the second month onward the comparison comes from the ledger row written by the run before it.

### Will this work if my customers pay in more than one currency?

Yes. Totals are grouped by currency rather than added together, and the summary says which currency each figure is in, so nothing gets silently mixed.

### What if I do not use Stripe invoices and only take one-off payments?

It still works. The collected, refunded and failed payment figures come from your payments, and the outstanding invoices section is simply left empty.

### Where do the files actually live, and can other people get to them?

They live in your General Input Storage store under a folder for each month, such as finance/2026-07/. The store is shared at the account level, so another workflow, an app, or a person in the dashboard can pick the same file up later.

Use this prompt in General Input: https://www.generalinput.com/prompts/month-end-stripe-finance-pack-stored-and-shared-in-slack