Skill Stripe Monitor
v1.0.2Stripe revenue dashboard in your agent — MRR, churn, new subscriptions, failed payments, and alerts. Use when the operator asks about revenue, subscriptions,...
Like a lobster shell, security has layers — review code before you run it.
Runtime requirements
skill-stripe-monitor
Live Stripe revenue metrics in your agent. MRR, new subscriptions, cancellations, failed payments, and top products — on demand or on a schedule.
When to Use
✅ USE this skill when:
- "What's my MRR?"
- "Any failed payments today?"
- "How many new subscribers this month?"
- "Show me my Stripe status"
/stripe mrr,/stripe failures,/stripe subscribers,/stripe status- Scheduled daily revenue briefing
❌ DON'T use this skill when:
- Modifying Stripe data (charges, refunds, plan changes) — this skill is read-only
- Detailed invoice reconciliation — use Stripe dashboard directly
- Tax reporting — use Stripe Tax or an accountant
Setup
1. Get your Stripe API key
Go to dashboard.stripe.com/apikeys.
Use a Restricted Key (recommended) with read-only access to:
customers— readsubscriptions— readinvoices— readcharges— readproducts/prices— read
Or use your Secret Key (sk_live_...) for full access — fine for personal use.
2. Store the key
Set the STRIPE_SECRET_KEY environment variable:
export STRIPE_SECRET_KEY="sk_live_..."
Use a Restricted Key scoped to read-only access for safety.
3. Verify connection
Ask your agent: "Check my Stripe connection" or run /stripe status.
Commands
| Command | What it does |
|---|---|
/stripe status | Quick health check — live mode, currency, recent activity |
/stripe mrr | Current MRR and 30-day trend |
/stripe subscribers | Active subscriber count, new this month, churn this month |
/stripe failures | Failed payments in the last 7 days |
/stripe top | Top 5 products/plans by revenue |
/stripe summary | Full daily summary (all of the above) |
How to Resolve the API Key
Before making any Stripe API call, read the key from the environment variable:
STRIPE_KEY="${STRIPE_SECRET_KEY:-}"
if [ -z "$STRIPE_KEY" ]; then
echo "ERROR: STRIPE_SECRET_KEY not set. See Setup section in SKILL.md."
exit 1
fi
All examples below assume $STRIPE_KEY is set via this resolution step.
API Patterns
All Stripe API calls use HTTP Basic auth — key as username, empty password:
curl -s -u "$STRIPE_KEY:" "https://api.stripe.com/v1/..."
Get MRR
Stripe doesn't expose MRR directly. Calculate it from active subscriptions:
# Fetch active subscriptions (paginate if needed)
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/subscriptions?status=active&limit=100&expand[]=data.items.data.price" \
| jq '
[.data[].items.data[] |
.price.unit_amount / 100 *
(if .price.recurring.interval == "year" then 1/12
elif .price.recurring.interval == "week" then 4.33
else 1 end)
] | add // 0 | . * 100 | round / 100
'
For more than 100 subscriptions, paginate using starting_after:
# Get all active subscriptions (handles pagination)
ALL_SUBS="[]"
LAST_ID=""
while true; do
PARAMS="status=active&limit=100&expand[]=data.items.data.price"
if [ -n "$LAST_ID" ]; then
PARAMS="${PARAMS}&starting_after=${LAST_ID}"
fi
RESP=$(curl -s -u "$STRIPE_KEY:" "https://api.stripe.com/v1/subscriptions?${PARAMS}")
PAGE=$(echo "$RESP" | jq '.data')
ALL_SUBS=$(echo "$ALL_SUBS $PAGE" | jq -s 'add')
HAS_MORE=$(echo "$RESP" | jq -r '.has_more')
[ "$HAS_MORE" = "false" ] && break
LAST_ID=$(echo "$RESP" | jq -r '.data[-1].id')
done
echo "$ALL_SUBS" | jq '
[.[].items.data[] |
.price.unit_amount / 100 *
(if .price.recurring.interval == "year" then 1/12
elif .price.recurring.interval == "week" then 4.33
else 1 end)
] | add // 0
'
New Subscriptions This Month
# Start of current month (Unix timestamp)
MONTH_START=$(date -u +%s -d "$(date +%Y-%m-01)" 2>/dev/null || \
date -u -j -f "%Y-%m-%d" "$(date +%Y-%m-01)" +%s)
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/subscriptions?status=active&limit=100&created[gte]=${MONTH_START}" \
| jq '.data | length'
Cancellations This Month
MONTH_START=$(date -u +%s -d "$(date +%Y-%m-01)" 2>/dev/null || \
date -u -j -f "%Y-%m-%d" "$(date +%Y-%m-01)" +%s)
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/subscriptions?status=canceled&limit=100&canceled_at[gte]=${MONTH_START}" \
| jq '.data | length'
Failed Payments (Last 7 Days)
SEVEN_DAYS_AGO=$(( $(date -u +%s) - 604800 ))
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/charges?limit=100&created[gte]=${SEVEN_DAYS_AGO}" \
| jq '[.data[] | select(.status == "failed")] |
{
count: length,
total_failed: ([.[].amount] | add // 0) / 100,
failures: [.[] | {id, amount: (.amount/100), currency, description, created}]
}'
Top Products by Revenue (Last 30 Days)
THIRTY_DAYS_AGO=$(( $(date -u +%s) - 2592000 ))
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/invoices?limit=100&status=paid&created[gte]=${THIRTY_DAYS_AGO}" \
| jq '
[.data[].lines.data[] | {product: .description, amount: .amount}]
| group_by(.product)
| map({product: .[0].product, revenue: ([.[].amount] | add) / 100})
| sort_by(-.revenue)
| .[:5]
'
Account Status Check
curl -s -u "$STRIPE_KEY:" "https://api.stripe.com/v1/balance" \
| jq '{
available: [.available[] | {currency, amount: (.amount/100)}],
pending: [.pending[] | {currency, amount: (.amount/100)}],
livemode: .livemode
}'
Full /stripe summary Response
When the operator asks for a full summary or /stripe summary, assemble and report all of the following:
💳 Stripe Summary — [Date]
MRR: $X,XXX
Active subscribers: XXX
New this month: XX (+X.X%)
Cancellations this month: XX (churn: X.X%)
Failed payments (7d): X ($XXX total)
↳ Most recent: [customer/description] — $XX — [time ago]
Top products (30d):
1. [Product name] — $X,XXX
2. [Product name] — $X,XXX
3. [Product name] — $X,XXX
Balance: $X,XXX available / $X,XXX pending
Mode: live ✅ (or test ⚠️)
If any metric fails to fetch, report it as "unavailable" and continue.
Scheduled Daily Summary
To receive a daily Stripe summary via Telegram, set up a cron-style heartbeat task or use OpenClaw's scheduler.
Example HEARTBEAT.md entry:
## Daily Stripe Summary
- Time: 08:00 CET
- Action: Run /stripe summary and send to Telegram
Or ask: "Set up a daily Stripe summary at 8am."
Alerts
Alert the operator when you detect:
| Event | Trigger | Message |
|---|---|---|
| Failed payment | Any charge with status: failed created in the last hour | ⚠️ Failed payment: $XX from [description] |
| New subscription | Subscription created in the last hour | 🎉 New subscriber! Plan: [name], $XX/mo |
| Cancellation | Subscription cancelled in the last hour | 📉 Cancellation: [customer], was on [plan] |
To check for recent events (last 60 minutes):
ONE_HOUR_AGO=$(( $(date -u +%s) - 3600 ))
# Recent events
curl -s -u "$STRIPE_KEY:" \
"https://api.stripe.com/v1/events?limit=50&created[gte]=${ONE_HOUR_AGO}&types[]=customer.subscription.created&types[]=customer.subscription.deleted&types[]=charge.failed" \
| jq '.data[] | {type, created, data: .data.object}'
For real-time alerts without polling, configure a Stripe Webhook (optional):
- Go to dashboard.stripe.com/webhooks
- Add endpoint: your OpenClaw webhook URL
- Select events:
charge.failed,customer.subscription.created,customer.subscription.deleted - Store the webhook signing secret as
STRIPE_WEBHOOK_SECRET(used to verify payloads)
Webhook support requires a publicly reachable OpenClaw instance. Polling (above) works without any webhook setup.
Error Handling
| HTTP status | Meaning | Action |
|---|---|---|
| 401 | Invalid API key | Ask operator to verify STRIPE_SECRET_KEY |
| 403 | Restricted key missing permission | Check key permissions in Stripe dashboard |
| 429 | Rate limited | Wait and retry with exponential backoff |
| 5xx | Stripe outage | Check status.stripe.com, retry later |
On 429, use this pattern:
RETRY_AFTER=$(curl -sI -u "$STRIPE_KEY:" "https://api.stripe.com/v1/balance" \
| grep -i "retry-after" | awk '{print $2}' | tr -d '\r')
sleep "${RETRY_AFTER:-5}"
Notes
- All amounts from Stripe are in smallest currency unit (cents for USD/EUR). Always divide by 100 before displaying.
- MRR calculation normalizes annual and weekly plans to monthly. This is an approximation.
- The Stripe API paginates at 100 items. For businesses with >100 active subscriptions, always paginate (see MRR pattern above).
- Test mode keys (
sk_test_...) work identically — the status check will flaglivemode: falseso you don't accidentally report test data as real.
Comments
Loading comments...
