# Revenue tracking

> Record purchases with mrkr.revenue(), what unit the amount is in, how zero amounts behave, and where the numbers show up on your dashboard.

Section: Tracking. Canonical page: https://mrkr.app/docs/tracking/revenue. Last updated: 2026-10-04.

`mrkr.revenue()` records a purchase amount against the current visitor and rolls it into your Revenue dashboard and per-source revenue breakdowns.

![The Revenue page showing total revenue and average revenue per visitor, with a daily series and a breakdown by traffic source.](https://mrkr.app/docs/revenue.webp)

Revenue events roll up into a total, a daily series, and an attribution breakdown by source.

## mrkr.revenue()

```js
mrkr.revenue(49.00, { plan: "pro", currency: "usd" });
```

The first argument is the amount; anything after it is an optional properties object, subject to the same limits as [custom events](https://mrkr.app/docs/tracking/custom-events).

## What unit is the amount?

Send whole currency units, such as `49.00` for 49 dollars, and an explicit three-letter `currency` such as `USD` or `CHF`. mrkr does not convert currencies. Sources and Revenue let you choose one reporting currency at a time; totals and source breakdowns never add different currencies together. Events without a usable currency appear separately as currency not recorded.

## How source attribution works

Revenue is credited to the traffic source captured on the session that sent the event. Sources shows visits, converting visits, conversion rate, revenue and revenue per visit together. It does not infer a first-touch or last-touch customer journey across sessions. Revenue without a matching session remains visible as Unattributed.

Revenue follows the time the event occurred within the selected range. A payment can belong to a session that began before that range, so a source can have revenue and zero new visits. Source and event filters apply to the matching session.

## Stripe payments

In Settings → Stripe payments, add your site's webhook URL to Stripe and save its signing secret in mrkr. Select `charge.succeeded`, `payment_intent.succeeded`, `checkout.session.completed` and `checkout.session.async_payment_succeeded`. Checkout revenue requires `payment_status=paid` and a PaymentIntent reference. Unpaid completions, failed payments and free trials do not become revenue. All four deliveries share one PaymentIntent receipt, preserving its original recorded money, currency and success-event time.

### Payment Links: no checkout code needed

Once Stripe is connected in mrkr, the tracker adds the current session as `client_reference_id` to `https://buy.stripe.com` links on your site. Existing merchant references are preserved. This identifies the original visit even if the customer never reaches your confirmation page. Stripe includes the reference in the paid Checkout Session webhook; see [Stripe's Payment Link parameters](https://docs.stripe.com/payment-links/url-parameters).

In Stripe → Payment Links → After payment, choose a redirect to a page on your tracked site, such as the URL below. The tracker submits only the Checkout Session ID and originating visit. mrkr links them after the signed paid webhook arrives, regardless of delivery order. It never trusts a browser-provided amount. This also works with custom Checkout links if their success URL includes the same placeholder.

Stripe success redirect:

```text
https://your-site.com/thanks?mrkr_checkout_id={CHECKOUT_SESSION_ID}
```

`?session_id={CHECKOUT_SESSION_ID}` is also supported. Keep the mrkr tracker on the return page. For links created inside custom JavaScript, call `mrkr.track('checkout_start')` before opening Checkout, or use the helper below, so this tab remembers the originating visit. Return matching lasts up to 24 hours after payment and requires a recorded visit that began before the payment. A visit first recorded on the return page cannot recover an earlier source.

### Checkout API: attach the session metadata

Capture metadata when checkout starts and send it to your server. Set it on the Checkout Session and, in payment mode, on its PaymentIntent as well. Session metadata is not automatically copied to PaymentIntent metadata; see [Stripe's metadata propagation rules](https://docs.stripe.com/metadata#exceptions). Either signed delivery can then identify the same recorded visit.

Browser: your checkout request:

```js
const metadata = window.mrkr?.checkoutMetadata() ?? {};
await fetch("/api/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ priceId: "YOUR_PRICE_ID", metadata })
});
```

Server: attach the session to the payment:

```js
const body = await request.json();
const sid = body.metadata?.mrkr_session_id;
const metadata = typeof sid === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(sid)
  ? { mrkr_session_id: sid }
  : {};

const session = await stripe.checkout.sessions.create({
  mode: "payment",
  line_items: [{ price: "YOUR_PRICE_ID", quantity: 1 }],
  success_url: "https://your-site.com/thanks?mrkr_checkout_id={CHECKOUT_SESSION_ID}",
  cancel_url: "https://your-site.com/pricing",
  metadata,
  payment_intent_data: { metadata }
});
```

Validate `metadata.mrkr_session_id` as a string of at most 128 letters, numbers, underscores or dashes on your server, and pass only that key. Keep prices and payment amounts under your server's control. For a same-origin native POST form, add `data-mrkr-checkout`; mrkr supplies a hidden `mrkr_session_id` field for your server to read. No other form values are collected.

For a PaymentIntent created directly, set its `metadata.mrkr_session_id`. mrkr links it only to an existing session belonging to the webhook's site, recorded before payment. Missing or other-site sessions leave revenue Unattributed; a source name alone cannot invent a visit. A paid subscription Checkout Session without a PaymentIntent reference cannot be reconciled by this webhook payload; its successful charge or PaymentIntent still records revenue. Recurring renewal attribution needs metadata on the payment itself.

> **Note:** Use either Stripe payment tracking or a browser revenue event for the same purchase. Sending both creates two independently recorded revenue events.

### Resend a missing payment

After correcting your webhook setup, open the original `payment_intent.succeeded` event in Stripe Workbench → Events and resend it to your site's mrkr endpoint. Stripe creates a fresh delivery signature; mrkr keeps the payment's original date and deduplicates its receipt. Check that date range and currency in Revenue. If the original payload lacks session metadata, the recovered payment appears as Unattributed.

## Where it shows up

Revenue events feed three places: the Revenue dashboard (total revenue, a daily trend, and a breakdown by traffic source), the overview page's headline revenue figure, and the purchase-conversion count used alongside your other goals.

> **Note:** An amount of `0` is recorded but contributes no revenue and is not a paid purchase. Purchases count positive revenue events; purchase rate counts visits with at least one positive payment, divided by visits in the range. A configured conversion can still count a zero-value event separately.
