Revenue Attribution

Revenue attribution connects the payments your customers make to the marketing that brought them to your site. Once your payments reach Spreeflo, every campaign, journey, email, web push, form and traffic source shows what it earned, not just how many people opened or clicked it.

What is revenue attribution?

A customer rarely buys on their first visit. They might find you through a Google search, come back a week later from your newsletter, and finally pay after clicking a web push. Revenue attribution looks at the visits a buyer made before paying and decides how much of the payment each of those visits earned. The credit then rolls up to whatever sent that visit: a channel, a source, a UTM campaign, or a Spreeflo campaign, journey, email or notification.

Spreeflo needs two things to do this:

  • Your visits, recorded by the Spreeflo SDK on your website. See Web Tracking & Analytics if you haven't installed it yet.
  • Your payments, sent from a connected payment provider (Stripe, Paddle, Lemon Squeezy or Polar) or from your own server through the Revenue API.
  • Why it matters

    Opens, clicks and visitors tell you what got attention. Revenue tells you what paid off. With attribution in place you can:

  • Spend where it pays. Compare channels and sources by the money they bring in, not by the traffic they send.
  • Judge campaigns by outcome. A broadcast with a lower click rate can still out-earn one with a higher one. Revenue and orders on each campaign show you which.
  • Find your best content. See which email, notification, page or form comes before purchases.
  • Know your customers. Every contact shows their lifetime spend, orders and refunds, and which visit earned each purchase.
  • Prove ROI. Report revenue per campaign, per recipient and per visitor instead of estimating it.
  • How it works

    Attribution happens in three steps:

  • Visits are tagged. Links in your campaign and journey emails and web pushes get UTM parameters plus a short Spreeflo tracking parameter (sf_t) that names the exact campaign and template. When someone clicks through, the SDK records the visit with its channel, source and campaign. UTM parameters you already put on a link are kept as they are.
  • A payment arrives. Spreeflo ties it to a person: by the visitor ID passed with the payment, or else by the customer's email address if it matches a contact in your audience.
  • Credit is assigned. Spreeflo looks at that person's visits within the lookback window before the payment and splits the credit using your attribution model.
  • Only visits that arrived through a channel count as touches. Direct visits (typed URLs, bookmarks) don't earn credit, so a payment with no earlier channel visit in the window shows up as Direct. Returning from a checkout page on Stripe, PayPal, Paddle, Lemon Squeezy, Polar or Dodo Payments also counts as Direct, so the payment page never takes credit for the sale.

    Only clicks earn credit. An email that was opened but never clicked, or a web push that was delivered but not tapped, earns no revenue. The recipient has to land on your site from the message.

    Attribution models

    Choose a model in Web Analytics > Settings > Attribution. The model decides how a payment is split between the visits before it.

    ModelHow credit is splitBest for
    Last-touch (default)The whole payment goes to the last channel visit before the purchase.Seeing what closes the sale. Simple and easy to explain.
    Linear (Professional plan)The payment is split evenly across every channel visit in the window.Longer buying cycles, where awareness and nurture campaigns deserve a share.

    Under Linear, a visit's share of a payment also counts as a share of an order, so a campaign can show fractional orders such as 2.5. The model in use is shown as a chip next to revenue in Web Analytics.

    Lookback window

    The lookback window, also in Web Analytics > Settings > Attribution, sets how far back a visit can be and still earn credit: 7, 30 (the default) or 90 days. With a 30-day window, a purchase on January 30 can be credited to visits from January 1 to 30. A longer window suits considered purchases and B2B, while a shorter one keeps credit on the campaigns that drove the decision.

    Credit is calculated when you open a report, so a change to the model or window shows up in your past revenue as well as new revenue.

    Attributed and influenced revenue

    Spreeflo reports two kinds of revenue, depending on whether the touchpoint brings people to your site or lives on it.

    Attributed revenueInfluenced revenue
    Applies toCampaign and journey emails, web pushes, transactional emails, and traffic sources.Forms (inline, hosted and popup) and the popups a campaign or journey shows.
    What countsPayments after a click that landed on your site, split using your model and window.Any purchase by the same person within the lookback window after they submitted the form or popup.
    Adds up?Yes. Each payment is shared out, so totals across rows never exceed your revenue.No. A purchase counts in full for every form and popup before it.
    RefundsSubtractedNot subtracted

    Connect a payment provider

    If you take payments with Stripe, Paddle, Lemon Squeezy or Polar, connect it once and payments flow in automatically. Revenue settings are per audience, so connect each audience that has its own website and payments.

  • Open Web Analytics and click the Settings (gear) button in the top right.
  • Go to the Revenue tab.
  • Under 1. Pick your payment provider, choose your provider.
  • Under 2. Connect, paste the credentials listed below and click Connect.
  • Spreeflo creates the webhook in your provider account for you and checks the signature of every event it receives. You don't need to copy a webhook URL anywhere. Disconnecting stops tracking right away and removes the webhook.

    ProviderWhat to pasteWhat's recorded
    Stripe A restricted API key (rk_live_…). Use the API key link in the form: it opens Stripe with the right permissions already selected. Don't change them. Paid Checkout sessions, PaymentIntents, paid invoices (subscriptions and renewals) and refunds.
    Paddle An API key (pdl_live_…) from Developer tools > Authentication, set to never expire, with Transaction (Read) and Notification settings (Read and Write). Completed transactions and approved refunds.
    Lemon SqueezyYour Store ID and an API key, both from your Lemon Squeezy settings.Orders, subscription payments and their refunds.
    Polar An organization access token (polar_oat_…) from Settings > General > Developer, with no expiry and the scopes checkouts:read, orders:read, organizations:read, products:read, subscriptions:read and webhooks:write. Paid orders and refunds.

    Each payment is recorded as a purchase event, before tax, from the moment you connect. Webhook retries and duplicate events for the same payment are recorded once. In the same tab, the Currency card sets the currency your revenue is reported in (see Currency).

    Connecting a provider brings your revenue in. To credit each payment to the visits before it, pass the visitor's Spreeflo IDs into the payment's metadata under the keys spreelytics_visitor_id and spreelytics_session_id. Read them in the browser when the visitor clicks to pay, and send them to your server with the checkout request:

    // In the browser, when the visitor clicks to pay
    const ids = {};
    const visitorId = Spreelytics.getVisitorId();
    const sessionId = Spreelytics.getSessionId();
    if (visitorId) ids.spreelytics_visitor_id = visitorId;
    if (sessionId) ids.spreelytics_session_id = sessionId;
    // Send `ids` to your server along with the checkout request
  • getVisitorId() returns null until the SDK has loaded, so read it at checkout rather than on page load, and leave the key out when it's null.
  • The session ID is optional. It ties the payment to the exact visit; the visitor ID alone ties it to the visitor.
  • Where the IDs go depends on how you take the payment:

    CheckoutWhere to put the IDs
    Stripe Checkout, one-time paymentBoth the session's metadata and payment_intent_data.metadata.
    Stripe Checkout, subscriptionsubscription_data.metadata. Every paid invoice, renewals included, then carries them.
    Stripe PaymentIntent without CheckoutThe PaymentIntent's metadata.
    Stripe Payment LinkNot possible: the link is the same for everyone. Payments are still recorded and matched by email.
    PaddlecustomData when you open the checkout with Paddle.js.
    Lemon Squeezy Custom checkout data: checkout[custom][spreelytics_visitor_id]=… on a checkout link, or checkout_data.custom when you create the checkout through the API.
    Polarmetadata on a checkout you create through Polar's API. Checkout links can't carry it.
    Anything elsevisitorId and sessionId in the Revenue API request (see Other payment providers).

    A Stripe Checkout session for a one-time payment, created on your server with the IDs from the browser:

    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{ price: 'price_123', quantity: 1 }],
      success_url: 'https://example.com/thanks',
      metadata: ids,
      payment_intent_data: { metadata: ids },
    });

    A Paddle checkout, opened in the browser:

    Paddle.Checkout.open({
      items: [{ priceId: 'pri_123', quantity: 1 }],
      customData: ids,
    });

    Other payment providers (Revenue API)

    Use the Revenue API if you bill through a provider that isn't listed (PayPal, Chargebee, Recurly, Shopify, a bank transfer, your own billing system) or record deals closed by hand. It's the Other option on the Revenue tab. You send each payment from your server, usually from the webhook handler your billing system already calls, and Spreeflo attributes it exactly like a connected provider's payments.

    Set it up

  • Create an API key. In Web Analytics > Settings > API keys (the same keys as Settings > API Keys), click Add API key. API keys are available on paid plans. Keep the key on your server: never put it in browser code.
  • Find your audience ID. Call GET /lists with your key. The id of the audience your website tracks into is the audienceId you'll send.
  • Pass the visitor ID to your checkout. Read Spreelytics.getVisitorId() when the visitor clicks to pay, as in Link payments to visits, and store it with the order: in your provider's custom metadata, a hidden form field, or your own order record. Your server can also read it from the spreelytics_vid cookie on a first-party request.
  • Send each payment to POST https://api.spreeflo.com/1.0/revenue once it succeeds.
  • curl -X POST https://api.spreeflo.com/1.0/revenue \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "audienceId": "123e4567-e89b-12d3-a456-426614174000",
        "eventName": "purchase",
        "revenue": { "amount": 79.99, "currency": "USD" },
        "transactionId": "order_1042",
        "email": "jane@example.com",
        "visitorId": "vid_9f8c1c2a3d4e"
      }'

    In practice you'll call it from the handler that already receives your billing system's "payment succeeded" webhook:

    // Your billing system's "payment succeeded" webhook
    app.post('/webhooks/billing', async (req, res) => {
      const order = req.body;
    
      await fetch('https://api.spreeflo.com/1.0/revenue', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.SPREEFLO_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          audienceId: process.env.SPREEFLO_AUDIENCE_ID,
          eventName: 'purchase',
          revenue: { amount: order.total, currency: order.currency }, // major units, before tax
          transactionId: order.id,
          email: order.customerEmail,
          visitorId: order.metadata.spreelytics_visitor_id, // saved at checkout
          sessionId: order.metadata.spreelytics_session_id,
          timestamp: order.paidAt,
        }),
      });
    
      res.sendStatus(200);
    });

    Request fields

    FieldRequiredNotes
    audienceIdYesThe audience to credit the revenue to.
    eventNameYesThe name the payment is recorded under, such as purchase or subscription_payment.
    revenue.amountYesA positive amount in major units (79.99, not 7999 cents), before tax.
    revenue.currencyYesA three-letter ISO 4217 code such as USD or EUR.
    transactionIdYesYour ID for the payment. Sending the same ID again records it once, so retries are safe.
    visitorIdNoThe visitor ID from Spreelytics.getVisitorId(). The most reliable way to credit the right visits.
    sessionIdNoThe session ID from Spreelytics.getSessionId(), to tie the payment to the exact visit.
    emailNoMatches the payment to an existing contact when there's no visitor ID. It never creates a contact.
    name, customerIdNoKept with the payment.
    renewal, isFreeTrialNoMark a subscription renewal, or a payment that starts a free trial.
    refunded, refundIdNoRecord a refund (see below).
    timestampNo ISO 8601 time of the payment. Defaults to now and can't be in the future. Set it when a payment reaches you late, so revenue lands on the right day.

    Unknown fields are rejected, so send only the fields above. The full reference is under Revenue in the API Reference.

    Record refunds

    Send the original transactionId again with refunded: true and a positive amount, never a negative one. With a refundId, the amount is that one refund, which lets you record several partial refunds. Without one, the amount is the total refunded so far. A refund can never exceed the original payment.

    {
      "audienceId": "123e4567-e89b-12d3-a456-426614174000",
      "eventName": "purchase",
      "revenue": { "amount": 20.00, "currency": "USD" },
      "transactionId": "order_1042",
      "refunded": true,
      "refundId": "refund_77"
    }

    Responses

    A successful call returns the transactionId and a message saying what happened:

    messageMeaning
    attributedRecorded and tied to a known visitor.
    recorded Recorded and counted in your revenue totals, but not tied to a visitor. Check that you're sending a visitorId.
    skipped (duplicate)This transactionId was already recorded. Nothing changed.
    refundedThe refund was applied.
    skipped (already refunded)This refund was already recorded, or the payment is fully refunded.
    transaction not foundA refund arrived before its payment. Send the payment first, then retry the refund.

    How payments are matched

    Whichever way a payment arrives, Spreeflo ties it to a person in this order:

  • Visitor ID. If the payment carries a visitor ID Spreeflo has seen, it's credited to that visitor's visits.
  • Email. Otherwise, if the email matches a contact in the audience, the payment goes on that contact and can be credited to their visits from devices where they were identified.
  • Neither. The payment still counts in your revenue totals but isn't credited to any campaign or channel.
  • A payment is never rejected for missing IDs. Passing the visitor ID gives the most accurate results, especially for buyers who never gave you their email before paying.

    Currency

    Payments keep their original amount and currency, and are also converted into your audience's reporting currency at that day's exchange rate. Set the reporting currency in the Currency card on the Revenue tab (USD by default). You can change it once a day. All existing revenue is then re-converted, and Web Analytics shows a notice until that finishes.

    Where to see revenue in Spreeflo

    Revenue shows up next to the numbers you already use to make decisions. Until an audience has recorded a payment, revenue columns show — and a tooltip on how to turn tracking on.

    Web Analytics

  • Key numbers. The Revenue, Revenue / visitor and Conversion rate tiles track your totals over the selected dates. Click a tile to chart it. Hover the revenue chart to see the top sources for each day; refunds show as a dashed cap on top of a bar.
  • Acquisition. Switch the Channels, Sources or UTM breakdown from Visitors to Revenue to rank them by attributed revenue. A chip shows the attribution model in use. UTM campaigns and UTM contents are where your Spreeflo campaigns and emails appear.
  • Pages, events, technology and location. In revenue mode, these show what the visitors behind each row spent in the selected dates. Use them to spot high-value pages and audiences. A buyer who viewed three pages counts on all three, so these rows overlap and don't add up.
  • Funnels. Hover a funnel step to see revenue per visitor at that step.
  • Users. Paying visitors are marked with a Revenue chip.
  • Goals. A goal on your payment event (purchase, or the eventName you send to the Revenue API) reports the revenue of its conversions.
  • Campaigns and journeys

    The Campaigns & Journeys list has Revenue and Orders columns for each channel. On the Email and Web push tabs they show attributed revenue from clicks. On the Forms tab they show influenced revenue from the campaign's popups. A campaign's report shows an Attributed revenue tile with its orders, and a row's Analytics action opens Web Analytics on that campaign's UTM breakdown in revenue mode.

    Emails and web push

    The Marketing emails, Transactional emails and Web push lists show a Revenue figure for each template. Its tooltip adds orders and revenue per recipient. Because revenue is counted per template, you can compare subject lines, designs and offers by what they earned.

    Transactional emails (receipts, onboarding, shipping updates) are credited only for links that point at your tracked website, so links to other sites, like password resets and file downloads, are never changed. Turn this off for a template with Credit revenue to this email in the template settings.

    Forms and popups

    The Forms list has a Revenue column showing influenced revenue: purchases by people within your lookback window after they submitted the form. Its tooltip also covers people who only viewed it. Use it to see which lead magnets and signup forms bring in buyers, not just leads.

    Contacts

    Each contact's profile has a Revenue card with their lifetime revenue after refunds, purchases, average order value, amount refunded, and first and last purchase dates. In their activity timeline, each purchase shows its amount and a Credited to line naming the campaign or channel whose visit earned it.

    Home

    The recent activity card on Home has a Revenue column for your latest campaigns and journeys.

    AI assistants

    Through the Spreeflo MCP server, Claude, ChatGPT and other AI clients can read revenue as well. Campaign and web push performance include revenue and orders, and traffic breakdowns include revenue per channel or source. You can ask something like "which of last month's campaigns earned the most per recipient?"

    Decisions you can make with revenue

    QuestionWhere to look
    Which channels deserve more budget?Web Analytics, Acquisition breakdown in Revenue mode, compared with Visitors mode.
    Is this campaign worth running again?Campaigns & Journeys list, Revenue and Orders columns, then the campaign report.
    Which email version should I keep?Marketing emails list, revenue per recipient in the Revenue tooltip.
    Are my receipts and onboarding emails selling?Transactional emails list, Revenue per template.
    Which form brings in buyers, not just leads?Forms list, Revenue column.
    Which landing pages are worth improving first?Web Analytics, Pages and Entry pages in Revenue mode, then Revenue / visitor.
    Who are my best customers?Contact profiles, Revenue card, and the Users tab in Web Analytics.

    Troubleshooting

  • Revenue columns show —. The audience hasn't recorded a payment yet. Check that the provider shows Connected on the Revenue tab, or that your Revenue API calls return recorded or attributed with the right audienceId.
  • Most revenue is under Direct. Payments aren't reaching Spreeflo with a visitor ID. Pass spreelytics_visitor_id in your checkout metadata, or visitorId in the Revenue API call. Make sure the SDK runs on the pages where checkout starts.
  • An email shows no revenue. Only clicks that land on the site where the SDK runs earn credit. Check that the email's links point there and that recipients clicked them before buying. For a transactional email, also check that Credit revenue to this email is on.
  • Revenue totals look too high. Influenced revenue (forms and popups) and the page and event breakdowns count a purchase on every row it relates to, so their rows don't add up. Attributed revenue (campaigns, emails, channels) does.
  • Numbers changed after a settings change. The attribution model and lookback window apply to past revenue too, so changing them restates your reports.
  • Need Immediate Help? Contact our support team at support@spreeflo.com or check our other guides for more information.