Every Sunday you promise to “look at the numbers.” Every Monday you ship a feature instead. A ten-minute founder script that calls your analytics HTTP API and prints one Slack message beats a dashboard you never open, as long as the script reads the same truth as your webhook + traffic stack.
This guide is part of the webhook ROI guides. It assumes revenue and visitors already land in one product via webhooks; the script is reporting, not ingestion.
What the script is for
Not for:
- Real-time launch monitoring (live launch dashboard).
- Sub-hour alerting on outages (use proper uptime tools).
For:
- Weekly rhythm: compare last 7 days to prior 7.
- Investor/async update: copy-paste a paragraph.
- Sanity check: API total vs Stripe dashboard within tolerance.
- Nudge: “RPV dropped; was traffic mostly docs?” without opening six tabs.
Minimal output (one screen)
Week 2026-W12 (Mar 16–22)
Visitors: 4,820 (+12% vs prior week)
New revenue: $3,140 USD (+3%)
RPV: $0.65 (was $0.71)
Top utm_source: newsletter (1,902 vis, $980 rev)
Top landing: /pricing (2,101 vis, $1,400 rev)
Pins: newsletter_issue_47 (active)Numbers are illustrative. Your API field names will differ. Consistency matters more than precision to the cent.
Auth patterns that do not leak
Bearer token in env var ANALYTICS_API_KEY, never commit.
Read-only scoped key if provider supports it, script cannot delete projects.
IP allowlist on cron host if offered.
Rotate keys when contractors leave. Scripts in private repo still leak in laptop backups.
Pseudocode structure
#!/usr/bin/env bash
set -euo pipefail
END=$(date -u +%Y-%m-%d)
START=$(date -u -d "$END -7 days" +%Y-%m-%d)
PREV_END=$(date -u -d "$START -1 day" +%Y-%m-%d)
PREV_START=$(date -u -d "$PREV_END -7 days" +%Y-%m-%d)
curl -sS -H "Authorization: Bearer $ANALYTICS_API_KEY" \
"$API_BASE/v1/summary?from=$START&to=$END" > /tmp/this.json
curl -sS -H "Authorization: Bearer $ANALYTICS_API_KEY" \
"$API_BASE/v1/summary?from=$PREV_START&to=$PREV_END" > /tmp/prev.json
node format-weekly.js /tmp/this.json /tmp/prev.json | mailx -s "Weekly" team@company.comUse Python, Go, or jq, boring is good. Run on GitHub Actions, Fly machine, or laptop cron if you are honest about uptime.
Endpoints you want (conceptual)
| Endpoint | Returns |
|---|---|
/summary?from&to | visitors, revenue, optional sessions |
/breakdown?dimension=utm_source | top N with revenue if joined |
/breakdown?dimension=landing_path | lander performance |
/pins | active annotations |
If API lacks revenue breakdown, compute top source from exported events weekly until product catches up, but push vendor for joined breakdown; that is why you unified webhooks in the main guide.
Idempotency and scheduling
Run Monday 07:00 local after UTC midnight has settled “yesterday.”
Use flock or workflow concurrency so double cron does not double-post Slack.
Include week_id in message; humans recognize ISO weeks faster than date ranges.
Hybrid billing in one API response
Your summary should sum normalized Stripe + Paddle + custom (Paddle alongside Stripe). Script prints optional line:
by_source: stripe $2,100 · paddle $1,040
Only when variance matters; avoid clutter when 100% Stripe.
Pair with marketing pins
If API returns pins, echo active pin names so the team remembers newsletter_issue_47 explains the traffic hump without re-debating. Complements marketing webhook markers.
Error handling without pager fatigue
- HTTP 5xx → retry 3× exponential; then single Slack “weekly script failed.”
- Partial JSON → fail loud; do not print zeros that look real.
- Compare
revenueto Stripe balance change loosely; if >20% off, append “verify webhooks.”
RPV math in script
rpv = revenue / visitors if visitors > 0 else 0
delta_pct = (this - prev) / prev if prev else nullApply same thin-traffic humility: if visitors < 500, append “(low sample).”
Security of Slack webhook URL
Incoming webhook URL is a secret. Env var SLACK_WEBHOOK_URL. Rotate if pasted in support ticket.
Prefer Slack app with bot token scoped to one channel if you graduate from incoming webhooks.
Version control
Store script in scripts/weekly-stats.sh with README: env vars, example output, owner. Future cofounder thanks you.
When to graduate to BI
If script exceeds 200 lines, adds cohort retention, and joins product DB, consider real BI. Until then, script is founder-appropriate.
Testing
- Dry-run against staging project with fake events.
--dry-runflag prints JSON without Slack.- After pricing change, verify week boundaries match pinned ranges.
Extending the script without becoming a data team
Add one dimension per quarter, not per week:
- Q1: summary + prior week percent deltas.
- Q2: top
utm_sourceand top landing path. - Q3: breakdown by billing
sourcewhen Paddle joins Stripe (hybrid guide). - Q4: echo active marketing pins from API (marketing markers).
If you crave cohort retention curves, you have graduated beyond founder script territory, good problem, different tool.
GitHub Actions example (sketch)
name: weekly-stats
on:
schedule:
- cron: "0 12 * * 1" # Monday 12:00 UTC
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/weekly-stats.sh
env:
ANALYTICS_API_KEY: ${{ secrets.ANALYTICS_API_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Secrets live in repo settings; never echo them in logs. concurrency: group: weekly-stats prevents duplicate posts if GitHub reruns the workflow.
When the API lies (and how to notice)
Scripts trust HTTP JSON. Lies enter when:
- Webhooks paused after key rotation you forgot.
- Timezone boundaries split Sundays wrong for US founders.
- Renewal events tagged as new sales.
Add a footer line: webhook_last_seen_at if API exposes it. Stale timestamp older than 48 hours on an active product means fix ingest before debating marketing.
Checklist
- Read-only API key in CI secrets
- Summary includes webhook-sourced revenue
- Prior week comparison in same message
- Low-sample guardrail
- Failure notifies one channel
Copy for investors (generated paragraph)
After numeric block, template a prose paragraph:
> “In the seven days ending {END}, we saw {VIS} visitors ({DELTA_VIS}% vs prior week) and {REV} in new revenue ({DELTA_REV}%). RPV was {RPV}. Largest traffic source was {TOP_SOURCE}; strongest landing path was {TOP_PATH}. Active campaign pin: {PIN}.”
Founders edit one adjective; the rest stays consistent week to week. Consistency builds trust faster than bespoke essays you skip when tired.
Local run vs cloud cron
Laptop cron dies when you travel. GitHub Actions is free-ish and good enough until you need sub-minute reliability. Pick one host, document owner, and alert if job misses two consecutive Mondays, that alert is often your first signal that API keys expired.
What not to automate yet
Do not auto-post weekly stats to public Twitter, numbers without context age poorly. Do not auto-change ad spend from script output unless you enjoy explaining mistakes to your accountant. Do not delete old scripts when API versions bump; archive with date in filename so you can diff field renames.
The script’s job is to start the meeting in your head, not to run the company while you sleep.
Cron on Monday 08:00
Schedule the script after coffee, before Slack, numbers land in your head before reactive work steals the week. Print the output to a private gist if you want a paper trail without building a dashboard.