Revenue Attribution
Wire two metadata fields into your Stripe object creates so FullVision links every paying customer to the visitor who acquired them
Pass two fields — fv_visitor_id and fv_user_id — on every Stripe Checkout Session, PaymentIntent, and Subscription you create. FullVision picks them up from the webhook and links the resulting customer to the visitor (and your authenticated user) automatically.
Use it when you have the tracker installed and payments are showing up with no channel attached to them.
This is the strongest customer-to-visitor signal we can read. It runs as a pre-pass before the email-based fallback, so when both signals are wired your attribution survives email typos, billing-vs-login splits, and address changes.
Prerequisites
- The FullVision tracker must be installed on your site (so visitors get a
fv_vcookie). See First-Party Tracker Proxy if you've enabled proxy mode. - Your app must have a stable
external_idfor each authenticated user — the same value you send asuser_idtoPOST /v1/events.
The two fields
| Field | Source | Required? |
|---|---|---|
fv_visitor_id | Raw value of the FullVision visitor cookie (fv_v), read server-side at create time | Send when present |
fv_user_id | Your app's authenticated user ID (the external_id you already pass to /v1/events) | Send when authenticated |
Send only what you have at create time. Either field alone is enough; both together is best.
The three Stripe creates
Set metadata on every Stripe Checkout Session, PaymentIntent, and Subscription you create.
const session = await stripe.checkout.sessions.create({
mode: "subscription",
customer: stripeCustomerId,
line_items: [...],
metadata: {
fv_visitor_id: visitorCookie,
fv_user_id: appUserId,
},
});If your checkout flow chains creates (e.g. a Checkout Session that creates a Subscription), set metadata on each independently. FullVision picks the latest non-null signal across all three sources, so duplication is safe and gives you a fallback if one webhook is delayed.
Do not put these fields on Charges, Refunds, or Invoices. Those objects are not in FullVision's metadata read path.
Sourcing the IDs
fv_visitor_id
Read the FullVision visitor cookie server-side at the moment you make the Stripe call. Pass the raw cookie value verbatim — no decoding, hashing, or transformation.
import { cookies } from "next/headers";
const visitorCookie = cookies().get("fv_v")?.value ?? null;If the visitor has no cookie yet (first request, blocked, brand-new session), pass null or omit the field. The visitor-only and user-only paths both work.
fv_user_id
Pass the authenticated user's stable ID — the same value you already send to /v1/events as user_id. This must match byte-for-byte.
const appUserId = session?.user?.id ?? null;If the user is not authenticated yet (anonymous checkout), pass null or omit the field.
What happens on each combination
fv_user_id | fv_visitor_id | What happens |
|---|---|---|
| present | present | Customer linked immediately on the next webhook |
| present | absent | Customer linked immediately (user ID wins) |
| absent | present, visitor known | Customer linked immediately via the visitor → user mapping |
| absent | present, visitor anonymous | Customer deferred — auto-resolves the moment the visitor authenticates and /v1/events arrives with both IDs |
| absent | absent | Falls back to email matching (the legacy path) |
The deferred case is what makes anonymous checkout work end-to-end. A visitor pays before signing up, the row sits unlinked, then the user authenticates and your existing /v1/events call provides the missing identity — FullVision picks up the deferred row automatically. No extra wiring.
Verification
After deploying, confirm the wiring is landing.
Are the metadata fields landing?
Look at a customer you know just checked out, through the documented People endpoint:
curl -H "Authorization: Bearer $FV_API_KEY" \
"https://data.fullvision.io/people?q=jane@acme.com"A customer whose metadata landed comes back with a real acquisition channel in
first_touch_channel rather than Non-attributed. If a brand-new payer is still
Non-attributed a few minutes after checkout, the metadata is not arriving.
Still seeing nothing?
If new payers keep arriving unattributed, the metadata is not reaching Stripe.
Check that metadata is genuinely being passed to your stripe.*.create calls —
log it once at the call site — and that the Stripe webhook destination points at
your FullVision workspace.
Things to avoid
Edge cases
How it relates to the email-based fallback
FullVision still links customers by email when metadata is absent — that path is unchanged and works for any customer who hits the system without metadata. The metadata path runs first; email is the fallback.
When you wire metadata, new customers get the stronger signal automatically. Existing customers keep working off email. There's nothing to migrate — just deploy the metadata change and the gradient improves over time.
