Web analytics fundamentals
How to attribute a sale to the page and source that produced it
By Theo Marsh 路 Oct 22, 2026 路 9 min read
Why visit counts do not answer this
Most analytics tools can tell you that an order happened and that some visits came from a given source. They cannot tell you that this order came from that source unless something connects the two. A split by traffic share will mislead you, because sources do not convert at the same rate.
Ahrefs published one example from its own site. Over 30 days ending in mid June 2025, AI search was 0.5% of its traffic and 12.1% of its signups. That is one site, one short window, and a signup rather than a payment, so treat it as an illustration of the gap and not a rate to expect on yours. The point is the shape: a source that looks negligible in a visits table can be where the outcomes come from.
Source: Ahrefs, Does AI Search Traffic Convert Better Than Traditional Search? For Ahrefs, Yes: 0.5% of Visitors Drove 12.1% of Signups, Jun 16, 2025. Retrieved Oct 19, 2026.
If you only read a visits table, you would cut the source that was earning. If you read the sale, you would not. That is the whole case for attributing at the order.
What attribution means in this post
One sale gets one page and one source. The page is the page view the buyer was on when you asked Stripe for a Checkout session. The source is the referrer that brought that visit to your site.
Other tools make a similar choice. Plausible's revenue documentation says the original source that brought the visitor to your site stays the one credited for the conversion, and that a payment gateway the buyer briefly visits is not credited. Plausible also converts amounts into the goal's reporting currency, and says revenue tracking is a Business plan feature. Peeko does neither of the last two: amounts in different currencies are listed, never converted, and every feature is on every plan size. If you are weighing the two tools, the Peeko and Plausible comparison goes through documentation and pricing in full.
Why a token and not a cookie
Stripe Checkout is a separate page. By the time the payment succeeds, your site's own state is gone from the picture, and what comes back to you is a Stripe event. Whatever you want to know about the buyer's visit has to travel inside the Checkout session itself.
Stripe gives you two fields for this. client_reference_id is a string of up to 200 characters meant for reconciling the session with your own records. metadata holds up to 50 key-value pairs, with keys up to 40 characters and values up to 500. When the buyer completes checkout, Stripe sends a checkout.session.completed event that contains the session's metadata.
The usual approach is a visitor ID stored in a cookie. Peeko does not use one. It sets no cookie, so the identifier has to come from somewhere else. The page token is that something. It is minted for each page view, it is good for 48 hours, and you pass it into the Checkout session as metadata. A verified sale then carries its page and its source.
Step 1: read the token on the page
On the page where the buyer clicks, read the token and send it to your server. This is the line from the Peeko revenue docs:
const token = window.peeko?.token?.() ?? null;
The optional chaining matters. If the script is blocked or has not loaded, token is null and the purchase still goes through, just without a label.
Wrap it in the call your Buy button already makes:
const token = window.peeko?.token?.() ?? null;
const res = await fetch("/api/checkout", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token }),
});
const { url } = await res.json();
window.location.href = url;
Read the token on the page the buyer is on, at the moment they click. If you read it on the pricing page, the sale is credited to the pricing page. If you want the credit to go to the article that sent them there, you need to carry the token across the click, which is a design choice and not something Peeko does for you.
Step 2: put the token in the Checkout session
On your server, create the session with the token as metadata. The key is peeko_t. This example uses Node 20 or newer, ESM, Express and the official stripe package:
import express from "express";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.json());
app.post("/api/checkout", async (req, res) => {
const token = typeof req.body.token === "string" ? req.body.token : null;
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: process.env.STRIPE_PRICE_ID, quantity: 1 }],
success_url: "https://example.com/thanks",
metadata: token ? { peeko_t: token } : {},
});
res.json({ url: session.url });
});
app.listen(3000);
Your Stripe secret key stays on your server, in your environment. Peeko never holds a Stripe API key. The only credential it stores for revenue is the webhook signing secret, which proves an event came from your account and does nothing else.
Two Stripe behaviors are worth knowing before you add more fields:
- Stripe does not copy metadata from one object to another on its own. The token you set on the session is on the session. If you want it on the PaymentIntent too, set
payment_intent_data.metadataas well. For a subscription, the equivalent issubscription_data.metadata. - Metadata is returned only to requests made with a secret key. Stripe redacts it from client-side requests, so the token does not leak back to the browser through Stripe.
Step 3: send the verified sale to Peeko
In Stripe, add a webhook endpoint and subscribe it to two events: checkout.session.completed and checkout.session.async_payment_succeeded. The Peeko docs are blunt about the second one: it is not optional. Without it, a delayed payment method such as a bank debit never reports its sale, because the first event fires before the money has arrived.
Stripe shows a signing secret that begins with whsec_ when you create the endpoint. Give that secret to Peeko and nothing else. Stripe signs every webhook with it in the Stripe-Signature header, and the signature is how Peeko knows the event came from your account and was not forged. Follow the revenue docs for where the endpoint URL and the secret go.
Stripe retries an undelivered event for up to three days in live mode, and says an endpoint may occasionally receive the same event more than once. That is why a sale should be counted by its identity and not by how many times a message arrived.
There is a second way to report revenue: a one line call from your own site.
window.peeko?.revenue?.({ amount: 49.99, currency: "USD", id: "order_1234" });
The amount is in whole currency units, and id is your order reference, used to avoid double counting. Pick one route per product. Peeko keeps the two totals apart and never adds them together, so using both for the same order will show you two numbers, not one inflated number.
Step 4: read the answer
Once a verified sale has come in with a token, it carries the page and the source. Without the token, the sale is still recorded but is labeled Unattributed. That label is your test. After the first real sale, check it. If every sale reads Unattributed, the token is not reaching the session: look at whether window.peeko.token returned null, and whether your server dropped the field before calling Stripe.
You can read the result in the dashboard, or ask the same question from a terminal or from an AI coding agent. The command line (npx @usepeeko/cli) speaks the same MCP endpoint an agent does, so a command and a tool call return the same numbers. The guide to connecting Claude Code or Cursor covers the setup. A useful first question is "which pages and which sources earned my last ten orders," because the answer is a short list you can check against Stripe by hand.
Where the chain breaks
Each link has a limit, and the limits decide how much of your revenue ends up labeled.
Static Payment Links cannot carry the token. A Payment Link is created once. Its metadata is copied to each Checkout session as a one time snapshot, so every sale through that link would carry the same value. A token is different on every page view, so the session has to be created in code at the moment of the click. Sales through a link you pasted into an email or a bio will be recorded and labeled Unattributed.
The token lasts 48 hours. That is more than enough when you read it at the click and create the session right away. It matters if you stash a token in a cart and create the session two days later. Read it fresh.
Checkout sessions expire too. Stripe lets you set a session to expire anywhere from 30 minutes to 24 hours after creation, with 24 hours as the default. A buyer who opens a session, leaves, and pays near the end of that window is still covered by a 48 hour token, but only because the token was minted before the session was created.
The source can be Direct. The source is whatever the referrer says, and some visits arrive with none. Visits from ChatGPT, Claude, Perplexity, Copilot and Gemini are counted by their referrer, and a visit that lost its referrer in transit shows up as Direct. A sale attributed to Direct is a statement about the referrer, not about the buyer. The post on counting visits from AI assistants by referrer explains which ones you can and cannot see.
One sale, one credit. This is single touch. A buyer who read three articles over two weeks is credited to the page view that produced the token, and the other two are not. If you need the earlier steps, a goal, a funnel or the returning visitor view show them, but they do not split revenue.
Currencies stay separate. A shop that sells in dollars and euros will see two lists. That is deliberate. Converting would add an exchange rate you did not choose to a number that is supposed to match your Stripe dashboard.
What this measurement does not tell you
It tells you which page view and which referrer sat behind a verified payment. It does not tell you why the buyer chose to pay, and it does not tell you what the page did compared with the pages they skipped. The Ahrefs figures above come from one site over 30 days, so they say that the gap between traffic share and outcome share can be large, not how large it will be for you.
If you want the same idea applied to a signup instead of a sale, with a funnel in between, the walkthrough from an AI referral to a signup without cookies uses the same token for the last step.
Checklist
- Read
window.peeko?.token?.()on the page where the buyer clicks. - Send it to your server and set
metadata: { peeko_t: token }on the Checkout session. - Subscribe the Stripe webhook to
checkout.session.completedandcheckout.session.async_payment_succeeded. - Give Peeko the
whsec_signing secret, and never the API key. - After the first real sale, look for the Unattributed label. If it is there, trace the token.