Skip to content

DataFast shows revenue as direct traffic? Pass the visitor id into checkout metadata

DataFast attributes a payment by reading datafast_visitor_id from the checkout's metadata. Read the cookie on the server when you create the Stripe, Polar, Lemon Squeezy or Dodo checkout.

DataFast3 min readships at docs/solutions/datafast/revenue-attribution-checkout-metadata.md

Tags: datafast · revenue-attribution · stripe · polar · lemon-squeezy · dodo-payments · checkout

You connected Stripe in DataFast. Revenue shows up. Every dollar of it says "Direct / None". Your Twitter thread that sold 40 licences gets no credit.

The payment reached DataFast. The visitor did not come with it.

How DataFast joins a payment to a visit

The script gives every browser a random id in a first-party cookie, datafast_visitor_id, plus a datafast_session_id. DataFast knows where that visitor came from: referrer, UTM tags, landing page.

The payment provider knows nothing about that cookie. It knows a customer and an amount. DataFast joins the two only if the checkout carries the visitor id in its metadata. No id, no join, "direct".

The cookie is first-party, so your server receives it on every request. Read it in the server action or route handler that creates the checkout.

Stripe Checkout

import { cookies } from "next/headers";

const jar = await cookies();
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  metadata: {
    userId: user.id,
    datafast_visitor_id: jar.get("datafast_visitor_id")?.value ?? "",
    datafast_session_id: jar.get("datafast_session_id")?.value ?? "",
  },
  success_url: `${origin}/billing?checkout=success`,
});

Put it on the Checkout Session's own metadata, not only on subscription_data.metadata. The session is what DataFast reads.

Polar

await polar.checkouts.create({
  products: [productId],
  metadata: { userId: user.id, datafast_visitor_id: visitorId },
});

Lemon Squeezy uses checkoutData.custom instead of metadata:

await createCheckout(storeId, variantId, {
  checkoutData: { custom: { datafast_visitor_id: visitorId } },
});

Dodo Payments takes metadata the same way. Dodo also needs a webhook to DataFast: in Dodo, Developer, Webhooks, add endpoint, choose DataFast, paste a DataFast API key.

For Stripe, Polar and Lemon Squeezy, no webhook is needed. Connect the provider in DataFast (Website settings, Revenue) and it reads the metadata itself.

Make it one helper

Three payment providers, one rule. Wrap it once:

export async function withCheckoutAttribution<T extends { metadata?: Record<string, string> }>(
  params: T,
): Promise<T & { metadata: Record<string, string> }> {
  const jar = await cookies();
  const metadata: Record<string, string> = { ...params.metadata };
  const visitor = jar.get("datafast_visitor_id")?.value;
  if (visitor && /^[A-Za-z0-9_-]{1,128}$/.test(visitor)) {
    metadata.datafast_visitor_id ??= visitor;
  }
  return { ...params, metadata };
}

Two details in there are deliberate:

  • Validate the cookie. It is client-writable. Anything that does not look like an id is dropped, not copied into your payment provider.
  • Never replace a key. Your webhook probably reads metadata.userId. The helper only adds.

Mistakes that look like they work

  • Taking the id from the request body. A client can post any id and credit any channel. Read the cookie on the server.
  • Also sending a payment event from the browser. The provider connection already records it. DataFast's manual payment call exists for flows with no connected provider, and it identifies by email. Pick one path, and prefer the one that keeps customer emails out of your analytics.
  • Tracking revenue as a custom goal. purchase_completed with an amount is a second, worse copy of the revenue DataFast already has.
  • Creating the checkout from a background job. No request, no cookie. Store the visitor id on the user row at signup and pass it from there.
  • Testing with a fresh browser that never loaded a page. No page view, no cookie, nothing to attribute.

Check it

  1. Visit the site from a link with ?ref=test.
  2. Buy something in test mode.
  3. In the provider's dashboard, open the session. datafast_visitor_id is in its metadata.
  4. In DataFast, the payment shows with test as its source.

If step 3 passes and step 4 fails, the provider is not connected in DataFast.