godozo × Stripe

Your Stripe day in one message

Jul 22, 2026 · Building godozo

Be honest about how many times you opened the Stripe dashboard today. Every visit was the same question — how are we doing? — and every answer could have been one message that finds you at 9pm instead. This post writes that message.

godozo · 9:00 PM
📊 stripe · last 24h
$1,284.00 gross · 23 payments · 2 failed
fees $37.52 · refunds $49.00 · +3 subs
available balance $8,420.16

The whole dashboard, in three lines, every night.

This closes out the Stripe trilogy. Part one pushed the events worth an instant buzz; part two put Approve/Deny buttons on the fraud calls. But most Stripe activity is neither — it's the routine renewals and fees that would bury your phone as pings and vanish unread as emails. The right channel for those is a digest: everything from the last 24 hours, rolled up, once. One notify, one cron line, and — because this is godozo — the same script answers on demand when you text it.


The digest script

No webhooks this time — a digest doesn't need to be told when things happen, it just asks. Stripe's balance transactions list is the ideal source: it's the money's own ledger, so every charge, refund, and fee that actually moved your balance is in one place with the fee already broken out.

// stripe-digest.mjs — npm i stripe, plus godozo (npm link the repo)
import Stripe from 'stripe';
import { createGodozo } from 'godozo';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const gd = createGodozo();
const usd = (c) => `$${(c / 100).toFixed(2)}`;
const since = Math.floor(Date.now() / 1000) - 86_400;   // last 24h

// Money that actually moved — the SDK's async iterator auto-paginates.
let gross = 0, fees = 0, refunds = 0, payments = 0;
for await (const t of stripe.balanceTransactions.list({ created: { gte: since }, limit: 100 })) {
  if (t.type === 'charge' || t.type === 'payment') { gross += t.amount; fees += t.fee; payments++; }
  if (t.type === 'refund') refunds += -t.amount;   // refund amounts are negative
}

// Failures never reach the balance, so count them off the charges list.
let failed = 0;
for await (const c of stripe.charges.list({ created: { gte: since }, limit: 100 }))
  if (c.status === 'failed') failed++;

let newSubs = 0;
for await (const _ of stripe.subscriptions.list({ created: { gte: since }, status: 'all', limit: 100 }))
  newSubs++;

const { available } = await stripe.balance.retrieve();
const bal = available.reduce((n, b) => n + b.amount, 0);

const message = [
  `${usd(gross)} gross · ${payments} payments · ${failed} failed`,
  `fees ${usd(fees)} · refunds ${usd(refunds)} · +${newSubs} subs`,
  `available balance ${usd(bal)}`,
].join('\n');

// --print for `godozo listen` (below); default is the nightly push.
if (process.argv.includes('--print')) console.log(message);
else await gd.notify({ title: '📊 stripe · last 24h', message });

Run it once by hand — node stripe-digest.mjs — and the card from the top of this post lands on your phone. (Single-currency accounts, note: amounts here assume one currency; if you charge in several, group by t.currency instead of summing blindly.)

Then make it nightly

The scheduler is whatever you already have. On the laptop or server where godozo runs, one crontab line:

# crontab -e · 9pm local, every day
0 21 * * * cd $HOME/myapp && /usr/bin/env node stripe-digest.mjs

That's the whole feature. No dashboard, no BI tool, no email report with an unsubscribe link. A script, a cron line, and a phone.


Why a digest beats checking

The daily summary looks like the least interesting post in this series. It's the one that changed my behavior most, for two reasons.

It ends the checking loop. Dashboard-checking is a slot machine — you pull because the answer might have changed. Once the answer reliably arrives on its own, the pull urge fades within days. Same effect the original Wendr heartbeat had: you stop reaching for the terminal because you trust the terminal to reach you.

It makes anomalies legible. After two weeks of digests you know your shape — around twenty payments, one or two failures, fees in the thirties. The night it says 19 failed, you don't need a monitoring rule to feel it; the number is simply wrong against fourteen nights of context your own memory built. Ambient numbers are anomaly detection for free.

And if you'd rather not even rely on your eyes for the big stuff, add a threshold escalation at the bottom of the script — a digest that can tell when it's carrying bad news:

if (failed >= 5) {
  await gd.notify({ title: '🔴 failure spike',
    message: `${failed} failed payments in 24h — check the payment page?` });
}

Bonus: text "numbers" and get the digest back

The 9pm push covers the routine. But sometimes it's 2pm and you just want to know — which is why the script has that --print flag. godozo listen turns any incoming text into a command run, and replies with whatever it prints:

godozo listen --exec 'node stripe-digest.mjs --print'

Text your bot anything — "numbers", "how are we doing", an emoji — and the current 24-hour digest comes back as the reply. Your Stripe account, on demand, from a beach. (Remember the one-poller rule: listen and a blocking gate can't share one bot token — give the digest bot its own token, or run one mode at a time. Outbound notify coexists with everything.)


Wire it in (paste into Claude Code)

Prompt — set up the nightly digest. Read ./llms.txt in the godozo repo. Create stripe-digest.mjs as described above: balance transactions for gross/fees/refunds, charges list for failures, subscriptions for new subs, one godozo notify with all of it, and a --print mode that writes to stdout instead. Run it once against my Stripe test-mode key and do not tell me you're done until I confirm the digest arrived on my phone with plausible numbers. Then show me the exact crontab line for 9pm local time — but don't install it until I say so.

The trilogy, assembled

Stack the three posts and you have a complete nervous system for the money side of a small business, in maybe 120 lines total:

Notice what's not in the stack: a SaaS dashboard, a metrics vendor, a cloud function. It's Stripe's own APIs, a phone, and a tiny Apache-2.0 relay whose two verbs — tell me, ask me — keep turning out to be all the infrastructure a human in the loop needs. Go make your money talk.

← More from the godozo blog