In short: every billing system can export payments as CSV. If each row carries the same user ID your backend uses for AI calls, you can compute AI margin per user without wiring up a single webhook: net revenue per user minus AI cost per user, over the same period.
This is the fastest way to answer "which users cost more than they pay?" for apps on Paddle, Lemon Squeezy, Gumroad, an invoicing tool, or a hand-kept spreadsheet, and for history from before you connected anything else.
| Column | Why |
|---|---|
id | The payment or invoice ID. Makes re-imports safe: the same ID is never counted twice |
user_id | The ID your backend sends with AI calls. Without it there is no join |
type | purchase, renewal, refund, trial_started, cancellation or expiration |
ts | When it happened (a date is enough) |
amount_usd | What the customer paid, or the amount refunded |
net_usd (optional) | What you kept after tax and fees; or give tax_pct and fee_pct |
period_end (optional) | When the paid period ends, so you know who is still a paying user |
id,user_id,type,ts,amount_usd,net_usd,product_id,period_end
in_001,user_42,purchase,2026-10-01,20,17.12,pro_monthly,2026-11-01
in_002,user_42,renewal,2026-11-01,20,17.12,pro_monthly,2026-12-01
re_001,user_7,refund,2026-10-12,20,20,pro_monthly,custom_data at checkout so it appears in the export; otherwise map Paddle customer IDs to your users first. Paddle is the merchant of record, so "earnings" is already net of tax and its fee: use it as net_usd.checkout[custom][user_id]. Like Paddle, it handles tax, so the payout amount is your net.metadata.user_id column. Or skip the CSV and import from Stripe directly.margin(user) = Σ net_usd (payments − refunds) − Σ ai_cost // same user, same periodAI cost per user comes from your backend: after each call, read the token counts and price them for the model (see the OpenAI and Anthropic guides). Use a period long enough to include each user's payment: monthly plans need at least 30 days, annual plans 90 days or more.
A CSV is a snapshot. Re-export each week or month and import the whole file again; with a stable id per row, rows already counted are skipped. When you outgrow that, send each payment from your backend as it happens with a revenue API, and keep CSV for backfills.
FiscaLLM takes this CSV as is. Upload it on the Setup page; common column names like customer, date and amount are recognised, bad rows are listed by line number, and re-uploading skips what is already there. Report AI usage with one line per call and you see margin per user, per plan and per trial, with alerts when someone starts losing you money.
It's free and open source (MIT) and can be self-hosted. Open the demo dashboard (sample data, no sign-up), read the CSV docs, or start free.
No. A CSV export with a user ID per row is enough to start. Webhooks only save you from re-exporting.
Map the billing system's customer ID or email to your own user ID first, or report AI usage under the billing customer ID. Either way both sides need the same ID.
Yes, as long as each payment comes from one source. Sending the same payment twice under different IDs counts it twice.