Skip to main content
Pro Plan15 minutesIntermediate

Setting Up Revenue Attribution

Configure revenue tracking and attribution - connect purchases to traffic sources with step-by-step implementation. Explore revenue setup and best practices.

revenueattributionsetuptracking
Last updated:
Pro Plan

Set up revenue attribution to track which marketing channels drive actual sales. Step-by-step implementation guide.

Prerequisites

Before starting:

  • Zenovay tracking script installed
  • Access to checkout/purchase code
  • Revenue goal enabled

Basic Setup

Simple Revenue Tracking

Track revenue when a purchase completes:

// On order confirmation page or in success callback
zenovay('revenue', 99.99, 'USD');

That's it for basic tracking!

With Goal Name

Associate with a specific goal:

zenovay('goal', 'purchase', {
  value: 99.99
});

Complete Implementation

Full Revenue Object

Include all relevant data:

zenovay('revenue', 149.99, 'USD', {
  // Recommended
  order_id: 'ORD-12345',

  // Optional - for detailed analysis
  items: [
    {
      id: 'SKU-001',
      name: 'Product Name',
      price: 49.99,
      quantity: 2,
      category: 'Electronics'
    },
    {
      id: 'SKU-002',
      name: 'Accessory',
      price: 50.00,
      quantity: 1,
      category: 'Accessories'
    }
  ],

  // Optional metadata
  coupon: 'SAVE10',
  shipping: 9.99,
  tax: 12.50
});

Implementation by Platform

Shopify

For Shopify stores:

  1. Go to Settings → Checkout
  2. Add to "Order status page":
{% if first_time_accessed %}
<script>
  zenovay('revenue', {{ total_price | money_without_currency }}, '{{ currency }}', {
    order_id: '{{ order_number }}',
    items: [
      {% for item in line_items %}
      {
        id: '{{ item.sku }}',
        name: '{{ item.title | escape }}',
        price: {{ item.final_price | money_without_currency }},
        quantity: {{ item.quantity }}
      }{% unless forloop.last %},{% endunless %}
      {% endfor %}
    ]
  });
</script>
{% endif %}

WooCommerce

For WordPress/WooCommerce:

  1. Add to your theme's functions.php or a plugin:
add_action('woocommerce_thankyou', 'zenovay_track_revenue');
function zenovay_track_revenue($order_id) {
    $order = wc_get_order($order_id);
    ?>
    <script>
    zenovay('revenue', <?php echo $order->get_total(); ?>, '<?php echo $order->get_currency(); ?>', {
        order_id: '<?php echo $order_id; ?>'
    });
    </script>
    <?php
}

React/Next.js

In your checkout success component:

import { useEffect } from 'react';

function OrderConfirmation({ order }) {
  useEffect(() => {
    if (order && window.zenovay) {
      window.zenovay('revenue', order.total, order.currency, {
        order_id: order.id,
        items: order.items.map(item => ({
          id: item.sku,
          name: item.name,
          price: item.price,
          quantity: item.quantity
        }))
      });
    }
  }, [order]);

  return <div>Thank you for your order!</div>;
}

Server-Side Tracking

Enterprise Plan

For secure server-side tracking, use the tracking endpoint with your tracking code:

// Node.js example - use the tracking endpoint directly
app.post('/api/order/complete', async (req, res) => {
  const order = await processOrder(req.body);

  await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Forwarded-For': req.headers['x-forwarded-for'] || req.socket.remoteAddress,
    },
    body: JSON.stringify({
      type: 'revenue',
      url: req.headers.referer || 'https://yoursite.com/checkout',
      revenue: order.total,
      order_id: order.id,
      currency: 'USD'
    }),
  });

  res.json({ success: true });
});

Attribution Configuration

Attribution Model

Zenovay doesn't make you commit to a single model up front. Open your website's dashboard, go to the Revenue tab, and switch the model directly on the Attribution card. Five models are available:

  • Last Touch (default)
  • First Touch
  • Linear
  • Position-Based
  • Time-Decay

You can change the model any time to look at the same data through a different lens — there's no separate "default model" or attribution-window setting to configure first.

Choosing an attribution model

The Revenue tab's Attribution card lets you switch between five models per report. Pick the one that matches the question you're asking:

ModelWhen to use it
Last-Touch (default)You want to know what closes deals
First-TouchYou want to know what drives discovery
LinearYou want a balanced, neutral split across the journey
Position-BasedYou want to reward both discovery (40%) and closing (40%)
Time-DecayYou have shorter sales cycles — recent touches weigh more

The model you pick is saved in the URL, so a refresh keeps it. If most conversions in a period came from a single session, the multi-touch models will look very similar to Last-Touch — that's expected and resolves as more multi-session journeys accumulate. For a closer look at how first-touch and last-touch differ, see First-touch vs last-touch attribution.

Why do all my attribution models show almost the same numbers?

This is expected and not a bug. When most conversions come from visitors who had a single session — or stayed on one channel — before converting, every model gives that channel 100% of the credit, so Last-Touch, First-Touch, Linear, Position-Based, and Time-Decay all look nearly identical.

The models start to diverge for visitors who genuinely moved across several different channels and sessions before converting; the more multi-session journeys you accumulate, the bigger the differences become.

Switching models will not change the AI channel — AI traffic is always credited to its own channel under every model.

Tip: to see the models differ most, compare First-Touch vs Last-Touch — they diverge whenever a visitor's first and final channels are different.

A conversion without a monetary value still counts toward conversion attribution; revenue figures only appear when revenue is attached to the goal.

AI as an Attribution Channel

Zenovay surfaces AI as a first-class channel in the Attribution panel, next to Direct, Organic, Paid, Social, and Referral. When a converting visitor was identified as AI traffic, that conversion is credited to AI instead of folding into Direct or a UTM channel.

Expand the AI row to break it down by AI product:

  • Named products — ChatGPT, Claude, Perplexity, Gemini, Copilot, DeepSeek and others, when the visit carried a recognizable AI referrer or UTM parameter.
  • Likely AI (unspecified) — visitors the Dark AI heuristic identified as AI-driven even though they arrived with no referrer. These can't be tied to one product, so they're grouped honestly under this label rather than guessed.

Keep in mind:

  • This is a conversions view. An AI product that sends visits but no attributable conversion in the selected period won't appear here — use the AI Influence view for AI traffic by product.
  • Seeing only Likely AI (unspecified) is accurate, not missing data: it means the period's AI conversions were heuristic-detected without a named referrer.
  • AI attribution appears on the authenticated dashboard; it is intentionally not shown on public or shared dashboards.

Cross-Device Attribution

Enterprise Plan

Track across devices:

  1. Identify users when they log in
  2. Journey stitched automatically
  3. Full path visible
// When user logs in
zenovay('identify', userId);

UTM Parameter Setup

Campaign Tracking

Ensure UTM parameters are used:

https://yoursite.com/?utm_source=google
                     &utm_medium=cpc
                     &utm_campaign=spring_sale
                     &utm_content=banner_1

UTM Parameters

ParameterPurposeExample
utm_sourceTraffic sourcegoogle, facebook
utm_mediumMarketing mediumcpc, email
utm_campaignCampaign namespring_sale
utm_contentAd variantbanner_a
utm_termKeywordsblue shoes

Testing Setup

Debug Mode

Enable debug logging:

zenovay('debug');

Then track a test purchase and check console.

Test Purchase

  1. Place test order
  2. Check browser console
  3. Verify in real-time view
  4. Confirm in revenue report

Verification Checklist

  • Revenue value correct
  • Order ID recorded
  • Items tracked (if sent)
  • Source attributed correctly
  • Currency correct

Handling Edge Cases

Recurring Revenue

For subscriptions:

// Track initial purchase
zenovay('revenue', 29.99, 'USD', {
  order_id: 'SUB-001',
  type: 'subscription',
  interval: 'monthly'
});

// Track renewals
zenovay('revenue', 29.99, 'USD', {
  order_id: 'SUB-001-RENEWAL',
  type: 'subscription_renewal',
  original_order: 'SUB-001'
});

Refunds

Handle refunds properly:

// Track refund
zenovay('revenue', -49.99, 'USD', {
  order_id: 'REFUND-ORD-12345',
  type: 'refund',
  original_order: 'ORD-12345'
});

Partial Shipments

If revenue is recognized at shipping:

// Track each shipment
zenovay('revenue', 50.00, 'USD', {
  order_id: 'ORD-12345-SHIP-1',
  parent_order: 'ORD-12345'
});

Data Quality

Prevent Duplicates

Ensure revenue isn't tracked twice:

// Using localStorage to prevent duplicates
function trackOrderOnce(order) {
  const tracked = localStorage.getItem(`tracked_${order.id}`);
  if (tracked) return;

  zenovay('revenue', order.total, 'USD', {
    order_id: order.id
  });

  localStorage.setItem(`tracked_${order.id}`, 'true');
}

Validate Before Sending

Check data quality:

function trackRevenue(orderData) {
  // Validate
  if (!orderData.value || orderData.value <= 0) {
    console.warn('Invalid order value');
    return;
  }

  if (!orderData.order_id) {
    console.warn('Missing order ID');
    return;
  }

  // Track
  zenovay('revenue', orderData.value, orderData.currency || 'USD', {
    order_id: orderData.order_id
  });
}

Troubleshooting

Revenue Not Appearing

If revenue doesn't show:

  1. Check tracking fires

    • Debug mode enabled?
    • Console errors?
  2. Verify goal setup

    • Revenue goal created?
    • Goal active?
  3. Check timing

    • After page fully loads?
    • In correct callback?
  4. Validate data

    • Value is number?
    • Not $0?

Attribution Missing

If source not credited:

  1. Check UTM parameters present
  2. Verify referrer not blocked
  3. Try a different attribution model on the Revenue tab's Attribution card
  4. Confirm the converting visit was tracked at all (check the real-time view)

Amounts Don't Match

If totals differ from accounting:

  1. Compare date ranges
  2. Check for refunds
  3. Verify currency handling
  4. Look for missing orders

Security Considerations

Client-Side Limitations

Be aware:

  • Values can be manipulated
  • Not for billing purposes
  • For analytics only

Server-Side for Accuracy

For critical accuracy:

  • Implement server-side tracking
  • Verify against backend
  • Use for reporting

Connect a Payment Processor (Server-to-Server)

If you already process payments through a supported processor, you can connect your account to Zenovay instead of editing your checkout code. Zenovay receives webhooks from the processor, attributes each captured payment to the originating session, and feeds the Revenue dashboard automatically — no client-side zenovay('revenue', …) calls required.

The Revenue settings tab lets you connect Stripe, PayPal, and Paddle. Each has a step-by-step setup guide:

ProcessorSetup guideWhat's tracked
StripeStripe revenue integrationCaptures, refunds, subscription invoices
PayPalPayPal revenue integrationCaptures and refunds
PaddlePaddle revenue integrationCaptures and refunds

Pick your processor on the Revenue settings tab, paste in your credentials, and Zenovay does the rest.

When to choose webhook ingestion over the JS tracker:

  • Server-side accuracy — webhook payloads are signed by the processor and can't be tampered with by browsers or ad-blockers.
  • No checkout-code changes — open your website's dashboard, go to the Revenue settings tab, choose your processor, and paste in your credentials.
  • Refunds handled automatically — when a refund is issued in your processor's dashboard, Zenovay records it.

When to keep using the JS tracker:

  • You need item-level data (items[]) that the processor doesn't include in webhooks.
  • Your processor isn't in the list above.
  • You want the order to appear in the real-time visitor view the moment the customer hits the success page (webhooks arrive a few seconds later).

You can use both approaches on the same site — Zenovay deduplicates by order_id, so the same order won't double-count.

Next Steps

Was this article helpful?