Revenue attribution
Record the first landing page and source of each new customer, and pass it to Stripe without personal data.
Relay can show which landing pages and channels bring paying customers, not only visitors. It uses one first-party cookie on your site and the metadata of your Stripe payments. Relay stores no names, emails, addresses or payment details.
1. Record the first touch
Copy attribution.ts into your app. It has no dependencies, and the browser part is under 1 KB minified.
On the first page view, startAttribution writes the cookie relay_ft with the landing path, the referring host, utm_source, utm_medium, utm_campaign and the time. The cookie expires after 90 days. It never overwrites an existing relay_ft cookie, so it keeps the first touch.
In a Next.js app, start it once in a client component in the root layout:
"use client";
import { useEffect } from "react";
import { startAttribution } from "@/lib/attribution";
export function Attribution() {
useEffect(() => {
const attribution = startAttribution({ consentRequired: true });
// Call this from your consent tool when the visitor accepts analytics.
window.addEventListener("analytics-consent", () =>
attribution.grantConsent(),
);
}, []);
return null;
}With consentRequired: true, nothing is written until grantConsent() runs. Use consentRequired: false only where you do not need consent for analytics cookies.
2. Pass it to Stripe
relayAttributionMetadata(cookieHeader) turns the cookie into Stripe metadata keys: relay_ft_path, relay_ft_referrer, relay_ft_source, relay_ft_medium, relay_ft_campaign and relay_ft_at.
Stripe Checkout. Add the metadata to the session and to the subscription it creates:
import { cookies } from "next/headers";
import { relayAttributionMetadata } from "@/lib/attribution";
const metadata = relayAttributionMetadata((await cookies()).toString());
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
metadata,
subscription_data: { metadata },
success_url,
cancel_url,
});Sign-up. If customers sign up before they pay, add the metadata to the Stripe customer that you create at sign-up:
await stripe.customers.create({
metadata: relayAttributionMetadata(request.headers.get("cookie")),
});3. Connect Stripe
In Stripe, create a restricted key with Read access to Invoices, Subscriptions, Checkout Sessions and Customers, and no write access. To also count refunds, give it Read access to Refunds and Charges. Then open Project settings > Conversion in Relay and paste it under Revenue from Stripe.
Relay checks the key before it saves it:
- It refuses secret keys (
sk_). Only restricted keys (rk_live_orrk_test_) are accepted. - It reads one invoice, to confirm read access.
- It sends Stripe a create request with an unknown field. A read-only key gets a permission error, so nothing can be created. If Stripe accepts the key for writing, Relay refuses it.
Relay encrypts the key. Disconnect deletes the key and all revenue data of the project.
4. Daily sync
The Revenue sync job in Automation runs once a day. It reads the paid invoices and refunds created since the last sync. The first sync reads the last 365 days.
- Each payment becomes one row: its kind (first payment, renewal, one-time or refund), amount, currency, time and first touch. A refund is a negative amount with the first touch of the payment it refunds.
- Relay reads the first touch from the subscription, then the Checkout session, then the customer.
- Relay stores only a salted hash of Stripe ids, so they cannot be matched across projects. It never stores names, emails, addresses or payment details.
- Each run reads at most about 5,000 invoices, in whole weeks. The rest follows in the next run.
- A run looks one week back, so a payment made late is still counted. Running it again adds no duplicate rows.
5. Reports
Open Experiments & Results. The Revenue by first touch section shows the last 30, 90 or 365 days.
- Coverage comes first: the share of new customers with attribution data. Under 80%, Relay shows a warning. Check the snippet and the Stripe metadata.
- For each currency, Relay shows revenue, new customers and the average first payment by channel and by first landing page (top 20).
- Revenue is payments minus refunds in the period. Relay does not convert currencies.
6. Priority and experiment results
When at least 80% of new customers in the last 90 days have attribution data, Relay uses revenue in two more places.
- Opportunity priority. The commercial score of a page is its first-touch revenue in 90 days divided by the 90th percentile across pages, at most 1. The page role score is the minimum. Revenue replaces the GA4 key-event score. The score explanation shows the amounts.
- Experiment results. An experiment shows the new customers and revenue whose first touch is the changed page, in the baseline and observation windows. Relay shows them only when each window has at least 10 new customers.
Payments can come weeks after the first visit. Relay counts each payment on its payment date, so a short observation window can miss later payments.
Channels
Relay sorts each first touch into one channel:
| Channel | Rule |
|---|---|
| Paid | utm_medium is cpc, ppc, paid or paidsearch. |
utm_medium is email. | |
| Organic search | The referrer is Google, Bing, DuckDuckGo, Yahoo, Ecosia or Brave Search, and utm_medium is empty or organic. |
| Referral | Another referring host, or a UTM source without a referrer. |
| Direct | No referrer and no UTM values. |
| Unknown | The payment has no attribution metadata. |