Skip to content
Docs

Billing and reporting

Billing and payouts

How payouts are priced, how conversions become ledger entries, statements, payout runs and rails, advertiser invoices, tax, compliance and accounting exports.

Money in Metrixed follows one rule: conversions are events, the ledger is the book. Every approved conversion becomes balanced journal entries, and statements, payout runs, invoices and the partner balance are computed from the ledger, so the dashboard, the report, the statement and the invoice agree.

1. Pricing a conversion

When ingest creates a conversion it prices it with the offer's payout and revenue entries. Selection is by priority, then specificity (partner, tier, geo, landing page or a variable selector, date window, schedule), then the default entry. Both sides are priced at once: payout to the partner and revenue from the advertiser.

payout_type / revenue_typeMeaning
fixedA fixed amount per conversion.
percent_of_saleA percentage of the reported amount.
fixed_plus_percentBoth.
per_click, per_milleClick-based models; they price clicks, not conversions, so conversions price to zero.
per_skuAmounts per product from the order lines, from the entry's own SKU table (fixed amount per unit and/or a percentage of the line). Lines are read from the postback the same way as the offer's SKU payouts below; an offer SKU payouts table, when a line matches it, prices the conversion after the entry.
tieredThe amount depends on the partner's volume: see tiered commissions below.
kpi_scaledScaled by a performance metric.
blankNo amount.

On top of entries:

  • Overrides (payout_overrides) target a partner, tier, geography, device, OS, SKU, hours or dates and win over every entry; fields set to inherit keep the entry's value, so you can change only the payout. An explain endpoint shows which override or entry priced a conversion and why.

  • Partner tiers apply a multiplier or absolute commission per tier. Tiers can be assigned automatically from rolling metrics by a nightly job, with hysteresis so partners do not flap between tiers.

  • Scrub moves a deterministic percentage of conversions to pending, rejected or scrubbed, per partner, offer or goal (the bucket is a hash of the conversion id, so replays give the same answer).

  • Customer-value rules vary payout with a customer's lifetime value or NGR ladder; referral commissions pay a parent partner a percentage; revshare runs restate percentage deals.

  • Multi-currency: amounts in another currency are converted with an exchange rate that is locked at conversion time and stored (fx_rate), so later rate moves never change history. Rates come from /billing/fx-rates or the frankfurter source (DT_FX_SOURCE).

  • Stored in your currency: every conversion's payout and revenue are also stored in the tenant currency at the rate of the conversion's day, so a report in your currency adds up exactly to the sum of its conversions. Conversions recorded before this existed are filled in by a background job (hourly, DT_TC_BACKFILL_*), month by month and audited; Settings → Data health shows any left ("Tenant-currency amounts stored per conversion").

See the conversion statuses in concepts: only approved conversions earn money.

Tiered commissions

Set Payout type to tiered on an offer's payout entry and add tiers in Offers → Payouts. Each tier has a start, an end (the last tier has none) and its own payout and revenue (an amount, a percentage of the sale, or both).

SettingValues
Counted metricconversions (default), revenue, sales (sale amount) or ftd (conversions of the entry's goal).
Periodmonth (default; months in your reporting time zone) or lifetime.
Count perpartner (default) or tier: all partners of the same partner tier share one count; a partner without a tier counts alone.

How it pays, with tiers 0–10 at 1.00 and 10+ at 2.50:

  • A conversion earns the tier that holds the count before it: approved conversions 1 to 10 earn 1.00, the 11th earns 2.50.
  • Only approved conversions count. A pending conversion shows the current tier's amount and is repriced, and counted, when it is approved. A rejected or reversed conversion is taken off the count.
  • Crossing a tier never changes conversions already paid (forward only).
  • Each conversion is counted once, even if the advertiser sends the postback again.
  • A payout sent on the postback, or a targeted override, keeps its own amount; the conversion still counts.
  • Approval reprices with everything that applied at arrival. When a pending conversion is approved, its whole pricing is run again exactly as it ran when the conversion arrived (the entry as it was then, the sale, the parameters, a pushed amount, the exchange rate, the partner tier's commission, a customer-value bonus), with one change: the tier in force at approval. The new amount reaches reports, the ledger, payout caps and the partner's postback once. Approving a conversion that a scrub rule held pays its price.
  • A tiered entry priced in another currency than the offer is converted at the rate of the conversion's day.

Per-SKU payouts

Offer ▸ Payouts ▸ SKU payouts prices conversions line by line when the advertiser sends a basket: sku[]=, qty[]= and price[]= (unit price) or amount[]= (line total) on the postback, items: [{sku, quantity, price}] in a JSON body, or sku=A,B&qty=2,1&sku_amount=20,5. Each row of the table is {sku, goal (optional), payout flat per unit or % of the line amount, revenue flat or %}; * matches every SKU without its own row and a goal-specific row wins. Each line is rounded to 6 decimals and the conversion's payout and revenue are the sums of the rounded lines, so the SKU report adds up to the stored amounts exactly. Lines matching no row are kept at 0; with no matching line the offer's normal payout applies.

2. The ledger

Accounts are named type:counterparty:currency. Journals are always balanced and carry an idempotency key, so reprocessing an event cannot double-post. The ledger worker turns events into journals:

EventEffect
Conversion approvedPartner earnings accrued (locked for lock_days, set per partner or by DT_LEDGER_DEFAULT_LOCK_DAYS) and advertiser receivable.
Lock period endsEarnings released to the payable balance.
Conversion reversed or rejectedReversal of the original journal, carried over if already invoiced.
AdjustmentManual or fraud clawback entries (reversible with /adjustments/{id}/revert).
Payout sent, invoice paid, top-upSettlement journals.

Each entry also records its amount in your currency (amount_tenant_ccy) at the rate of the conversion's day, the same amount the conversion and your reports carry, so the ledger and reports agree to the cent even after a rate changes; a reversal converts at that same rate and cancels its approval exactly.

Test traffic never posts. The API exposes /ledger/entries and /ledger/balances, and GET /tools/ledger-trace explains how a conversion reached its balance.

3. Statements and payout runs

  • Statements group a partner's lines for a period with carryover and withholding. POST /billing/statements generates; ?include=html renders a deterministic HTML (and a dependency-free PDF). Partners read theirs in the partner portal (/partner/statements).
  • Payout runs (/billing/payout-runs) select partners whose approved balance passes their minimum, show a preview, and are approved by a second person where policy requires (maker-checker). Approving a run executes payments.
  • Rails. Execution uses the partner's payment method and a provider adapter: PayPal Payouts, Payoneer, Tipalti, Wise, Stripe Connect and a generic crypto provider, or manual bank files (SEPA pain.001, NACHA, an India bank format). Every item carries a stable reference used as the provider's idempotency key, so a retried batch never pays twice. Provider webhooks (/payout-webhooks/{tenant}/{provider}) update item status. Payment-method secrets are sealed with DT_SECRETS_KEY.

4. Advertiser billing

  • Invoices (/billing/advertiser-invoices) are generated from approved revenue, with tax rules for domestic, reverse charge, export and GST cases. Payments (full or partial) are recorded with POST .../payments; the older /pay is for whole-invoice payment.
  • Prepaid advertisers draw down a balance topped up through POST /billing/topups (served by the API but not yet described in the OpenAPI contract; the advertiser portal can use Stripe Checkout), with a credit limit, a grace percentage and optional automatic pausing of offers when the balance runs out.
  • Dunning sends reminder emails on a schedule and can suspend an advertiser after a number of days; the scheduler runs hourly (DT_DUNNING_TICK_SECONDS).

5. Compliance

  • Tax IDs: VAT (VIES), UK HMRC and Indian GSTIN are validated by format and checksum; live online checks are optional (DT_TAX_ONLINE=1).
  • Tax forms and withholding: partner W-8/W-9 and equivalents; without a verified form the configured backup withholding applies. Withholding is stored on the partner (billing.withholding_percent) and applied on statements.
  • KYC and sanctions checks are modelled with pluggable providers; yearly 1099-NEC and DAC7 exports are generated from paid statements.
  • Accounting exports render QuickBooks (IIF and CSV), Xero (CSV), NetSuite (CSV) and Tally (XML) files for partner statements (bills), advertiser invoices (sales) and payouts, deterministically ordered so exports can be diffed and re-run.

In the admin portal these live under Money (Billing, Advertiser invoices, Payout runs, Payment methods, Tax & compliance, Accounting exports, Dunning); partners and advertisers see their own parts in their portals.

What is not covered yet

PDF branding of statements and invoices, and live BigQuery or provider integrations that were only verified against fakes, are noted in the repository handoffs; plan a test payout with each rail before relying on it.