Normalized MRR and ARR
Weekly, monthly, quarterly, semiannual and annual compared after normalization. Moving 99 a month to 1,800 a year is 51 of expansion, not 1,701.
DuckLedger is the revenue-metrics layer that also knows the other half of the equation. Normalized MRR, a movement waterfall, cohorts, churn and retention; cloud, AI APIs, expenses and suppliers. And the answer that only exists once both sides close: margin by product, by customer, by user.
Multi-product · RBAC · Encrypted secrets · No spreadsheet archaeology
Sample · current month
MRR
R$ 248.4k
Gross margin
63.3%
Cost / user
R$ 3.12
LTV:CAC
4.1×
Full revenue metrics
MRR, ARR, waterfall, NRR, GRR, cohorts and conversion funnel.
Every cost has an owner
Cloud, AI and expenses allocated to the product that caused them.
Margin per user
Revenue minus direct cost, down to the person and the session.
A revenue tool knows what came in. A FinOps tool knows what went out. Neither closes the account for one specific customer. These are DuckLedger’s questions.
Revenue tools stop at stage 3. Cloud-cost tools start at 4 and never learn what came in. “Is this customer profitable?” lives exactly at the joint — and needs revenue and cost for the same product, in the same period, in the same currency.
Charge
Subscription
Analytics
Cost
Allocation
Profit
MRR, churn, cohorts
cloud cost by tag
crosses the joint
The gateway moves the money at stage 1. DuckLedger explains the five that follow.
Not a pretty MRR chart on top of a CSV. A ledger of movements, daily snapshots and outcomes written when they happen — because a canceled subscription no longer remembers what it was worth.
Weekly, monthly, quarterly, semiannual and annual compared after normalization. Moving 99 a month to 1,800 a year is 51 of expansion, not 1,701.
New, expansion, reactivation, contraction and churn — with voluntary churn separated from involuntary by the status the subscription lapsed from.
Cohort retention, ARPU, average lifetime, and NRR/GRR anchored to the snapshot the month opened on.
Outcomes written once and never recomputed. Rate over resolved, with the still-open population always shown beside it.
RevenueCat covers Apple, Google and web at once; App Store and Play also connect directly. Store commission and refunds kept apart from gross.
Asaas built in: Pix, boleto, cards and subscriptions, with idempotent webhooks and per-gateway-account reconciliation.
Customer attributes, saved segments, and a revenue forecast built from observed movements.
Every payment resolved to the product that earned it — never “somewhere in the gateway.”
A cost with no product never becomes margin. DuckLedger imports spend where it originates, attributes it to a workspace, and forces a question accounting never asks: does this serve a customer, buy one, or neither?
Automatic sync of spend by service, written as an expense on its competence date and allocated by product.
OpenAI, Anthropic, OpenRouter, Gemini and more, with a daily sync and attribution by project, key and workspace.
Manual, recurring and CSV-imported, with supplier, category and competence date.
Cost of revenue, acquisition or overhead. Every category starts as overhead, so an unclassified one never silently inflates margin or CAC.
Shared cost stays at the organization; each workspace’s P&L stays clean and comparable.
An auditable formula engine: total cost over active users, operations or requests — whichever metric you define.
A revenue LTV tells you what a customer will pay. Only LTV on margin tells you what you keep — the one version comparable to what acquiring them cost. Four figures no revenue-only tool can produce.
gross margin = (revenue − cost of revenue) / revenue CAC = acquisition spend / new customers LTV (margin) = ARPU × average lifetime × gross margin LTV:CAC = LTV (margin) / CAC payback = CAC / (ARPU × gross margin)
A month that acquired nobody does not have a CAC of zero — it has no CAC. A negative margin has no payback period. The figure comes back null instead of lying.

Everyone charts revenue retention by cohort. Here the cohort accumulates gross profit until it covers what acquiring it cost — and the month that happens has a name.
Losing a high-margin customer and a negative-margin one are not the same event. One of them improves the company.
The margin rate comes from the P&L; recurring revenue lives on its own basis. Carrying the rate into MRR is standard practice — adding cash revenue to MRR is the error this separation exists to prevent.
Average cost per active user is a division, not a measurement. Instrument what the product actually does, and every model call, job and conversation gets an owner, a session, a currency and an amount.
One ingest key and one package. Batching, retries and idempotency on the event ID.
Usage already lives in your tables: one read-only view and a scheduled query.
Already-standardized events import directly with host, project and a personal key.
await usage.withTrace(
{
kind: 'conversation',
name: 'Support chat',
subject: user.id,
},
async (trace) => {
trace.step('retrieveContext');
usage.track({
type: 'ai.tokens_spent',
data: {
model: 'gpt-4o',
prompt_tokens: 1200,
completion_tokens: 400,
},
});
},
);Tokens with no cost attached are priced from public list rates and marked estimated. Pass a measured cost and it replaces the estimate.

Conversation, checkout or job: every step, provider and service hop on one row, with duration and cost.
What the provider charged and what the price catalog computed carry different labels on the same screen.
A trace that burned USD on a model and BRL on a gateway fee cost two things. Picking an exchange rate to merge them is your decision, not the cell’s.
Your app’s internal ID becomes the identity, and one account can pay for many users without multiplying the revenue.
This is not modesty — it is what separates a decision dashboard from a decorative one. When the basis is missing, DuckLedger says it is missing.
Spending R$ 3,000 and acquiring nobody is not free acquisition. With no basis the answer is null, and the screen says what still needs connecting.
A cost derived from list prices never dresses up as a measured one. The same holds for trace names inferred from the route.
Sandbox and production are filed under the environment the event carries, not the one the connection was set to.
Idempotent webhooks, store aliases resolved to a single customer, and instrumented cost reconciled against the invoice rather than added to it.
Compared by category of tool rather than by brand — the difference is not quality, it is scope.
MRR, churn, cohorts
Apple and Google subscriptions
Pix, boleto and BR billing
Cloud cost by product
AI API cost
Margin and profit by product
LTV on gross profit
Cost and revenue per user
Answer the same day
No six-week implementation. Wire the structure once, and the numbers compound on their own.
One organization, one workspace per product. Shared cost stays at the org; each product’s P&L is comparable from day one.
Asaas for Pix, boleto and cards; RevenueCat or the stores for in-app purchase. Backfill once, webhooks for the rest.
AWS Cost Explorer, AI providers and expenses. Classify each category as cost of revenue, acquisition or overhead.
SDK, database or PostHog. From there, “what does this user cost and earn” has an answer with a name and a session.

An ingest key lives on your server and its scope is deliberately narrow: send events, read the meter catalog, register a missing type. Nothing else.
If you run digital products and the revenue answer lives in one tool, the cost in another, and the margin in none — that joint is what DuckLedger closes.
Already on the team? Sign in and pick up where you left off.