Revenue Attribution
Revenue attribution is the core of Zori - connecting every dollar of revenue to the marketing channel that brought the customer.
The Attribution Chain
Section titled “The Attribution Chain”Traffic Source → Visitor ID → Payment Metadata → Revenue Attribution ↓ ↓ ↓ ↓ (utm_source) (cookie) (zori_visitor_id) (twitter: $500)Step 1: Capture Traffic Source
Section titled “Step 1: Capture Traffic Source”When a visitor arrives, Zori captures their origin:
// Visitor arrives from: yoursite.com?utm_source=twitter&utm_campaign=launch
// Zori automatically captures:{ utm_source: "twitter", utm_campaign: "launch", utm_medium: "paid_social", referrer: "https://twitter.com/...", landing_page: "/pricing"}Step 2: Assign Visitor ID
Section titled “Step 2: Assign Visitor ID”A unique zori_visitor_id is assigned and stored as a cookie:
Cookie: zori_visitor_id=vis_a1b2c3d4e5f6Expiry: 2 yearsThis ID persists across sessions, even if the visitor returns days or weeks later.
Step 3: Track the Journey
Section titled “Step 3: Track the Journey”All subsequent activity is linked to this visitor ID:
// Page views{ visitor_id: "vis_a1b2c3", event: "page_view", page: "/pricing" }{ visitor_id: "vis_a1b2c3", event: "page_view", page: "/features" }
// Clicks{ visitor_id: "vis_a1b2c3", event: "click", element: "Sign Up button" }
// Sessions{ visitor_id: "vis_a1b2c3", session_id: "ses_xyz", duration: 542000 }Step 4: Pass to Payment
Section titled “Step 4: Pass to Payment”When the visitor makes a purchase, include their visitor ID:
// Frontend: Get the visitor IDconst visitorId = await window.ZoriHQ.getVisitorId();
// Frontend: Send to your serverconst response = await fetch('/api/create-checkout', { method: 'POST', body: JSON.stringify({ items: cartItems, zori_visitor_id: visitorId // Include this! })});// Backend: Include in Stripe metadataconst session = await stripe.checkout.sessions.create({ line_items: items, metadata: { zori_visitor_id: req.body.zori_visitor_id }, success_url: 'https://yoursite.com/success', cancel_url: 'https://yoursite.com/cart'});Step 5: Receive Payment Webhook
Section titled “Step 5: Receive Payment Webhook”Stripe sends a webhook when payment succeeds:
{ "type": "charge.succeeded", "data": { "object": { "id": "ch_xxx", "amount": 9900, "metadata": { "zori_visitor_id": "vis_a1b2c3d4e5f6" } } }}Step 6: Attribute Revenue
Section titled “Step 6: Attribute Revenue”Zori processes the webhook:
- Extracts
zori_visitor_idfrom metadata - Looks up the visitor’s first-touch data
- Links the $99 payment to the original traffic source
Result: Twitter campaign credited with $99 revenue.
Where to Pass Visitor ID
Section titled “Where to Pass Visitor ID”Stripe Checkout
Section titled “Stripe Checkout”const session = await stripe.checkout.sessions.create({ metadata: { zori_visitor_id: visitorId }});Stripe Subscriptions
Section titled “Stripe Subscriptions”const subscription = await stripe.subscriptions.create({ customer: customerId, items: [{ price: 'price_xxx' }], metadata: { zori_visitor_id: visitorId }});Stripe Payment Intents
Section titled “Stripe Payment Intents”const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, currency: 'usd', metadata: { zori_visitor_id: visitorId }});Stripe Invoices
Section titled “Stripe Invoices”For subscriptions, also add to the subscription metadata:
// The subscription metadata flows to invoice paymentsconst subscription = await stripe.subscriptions.create({ metadata: { zori_visitor_id: visitorId // This metadata appears on invoices }});Common Patterns
Section titled “Common Patterns”User Signup Flow
Section titled “User Signup Flow”// 1. User signs upasync function handleSignup(email, password) { const visitorId = await window.ZoriHQ.getVisitorId();
// Store visitor ID with user account await createUser({ email, password, zori_visitor_id: visitorId });
// Identify the visitor await window.ZoriHQ.identify({ app_id: newUser.id, email });}
// 2. Later, when they purchaseasync function handlePurchase(userId) { const user = await getUser(userId);
const session = await stripe.checkout.sessions.create({ customer_email: user.email, metadata: { zori_visitor_id: user.zori_visitor_id // Use stored ID } });}Anonymous Checkout
Section titled “Anonymous Checkout”// No account needed - just pass visitor IDasync function handleAnonymousCheckout(cart) { const visitorId = await window.ZoriHQ.getVisitorId();
const session = await stripe.checkout.sessions.create({ line_items: cart, metadata: { zori_visitor_id: visitorId } });}Upgrading Free Users
Section titled “Upgrading Free Users”// User signed up free, now upgradingasync function handleUpgrade(userId, planId) { const user = await getUser(userId);
// Even if they signed up months ago, use original visitor ID const subscription = await stripe.subscriptions.create({ customer: user.stripe_customer_id, items: [{ price: planId }], metadata: { zori_visitor_id: user.zori_visitor_id } });}What Gets Attributed
Section titled “What Gets Attributed”| Stripe Event | Attributed |
|---|---|
charge.succeeded | Yes |
invoice.payment_succeeded | Yes |
checkout.session.completed | Via charge |
subscription.created | Via first invoice |
Troubleshooting
Section titled “Troubleshooting”Revenue Not Appearing
Section titled “Revenue Not Appearing”- Check metadata: Is
zori_visitor_idin the Stripe payment? - Check webhook: Is Zori receiving Stripe webhooks?
- Check visitor: Does the visitor exist in Zori?
Wrong Attribution
Section titled “Wrong Attribution”- Check timing: Did the visitor ID exist before the first touch?
- Check cookies: Are cookies being blocked?
- Check identification: Was
identify()called incorrectly?
Missing Visitor ID
Section titled “Missing Visitor ID”// Always have a fallbackconst visitorId = await window.ZoriHQ.getVisitorId();
if (!visitorId) { console.warn('No visitor ID available - attribution will be lost'); // Consider: generate a fallback ID, or proceed without}Best Practices
Section titled “Best Practices”- Store visitor ID at signup - Don’t rely on the cookie still existing
- Pass to every payment - Subscriptions, one-time, upgrades
- Call identify() on login - Links returning users to their history
- Test the flow - Verify attribution is working end-to-end
Next Steps
Section titled “Next Steps”- Stripe Integration - Complete setup guide
- How It Works - System architecture
- JavaScript API - API reference