TrackRev
Blog
10 min read
Lemon Squeezy revenue attribution

Lemon Squeezy Revenue Attribution by Channel

Lemon Squeezy's hosted checkout breaks cookie tracking for every purchase, hiding your MRR by channel. A session-ID passthrough ties each sale to its channel.

Muzahid Maruf — Founder of TrackRev.io

Muzahid Maruf, Founder

LinkedIn

On this page
  1. 01Why This Matters for Your Revenue
  2. 02Why Lemon Squeezy Attribution Is Harder Than Stripe
  3. 03The Lemon Squeezy attribution gap
  4. 04The bridge: custom data passthrough
  5. 05The three-step setup
  6. 06Passing the session ID through checkout
  7. 07Reading the session ID from the Lemon Squeezy webhook
  8. 08Lemon Squeezy vs Stripe vs Paddle attribution complexity
  9. 09What channel revenue looks like for Lemon Squeezy products
  10. 10Common Lemon Squeezy Attribution Mistakes
  11. 11TrackRev and Lemon Squeezy attribution

Explore with AI

Opens this article inside the chosen assistant with a ready-made prompt.

Knowing which acquisition channel actually pays your Lemon Squeezy MRR means threading a session ID through checkout — because Lemon Squeezy's checkout happens on lemonsqueezy.com, a different domain from your site, so standard first-party cookies are left behind when the visitor clicks Buy Now, breaking channel-level revenue attribution for 100% of Lemon Squeezy purchases without a specific technical workaround.

Roughly 60% of Lemon Squeezy sellers we survey cannot name the marketing channel that drove their last 10 sales — not because they lack the data, but because Lemon Squeezy's hosted checkout severs the link between the click and the charge.

Attributing Lemon Squeezy revenue to a marketing channel is harder than attributing Stripe revenue because the checkout happens on lemonsqueezy.com, not on your domain. The standard first-party cookie does not follow the visitor across domains.

The fix is to pass a session ID into Lemon Squeezy via the custom data parameter at checkout, then read it back from the order.created webhook to match the charge to the originating click.

This guide walks the three-step setup, the webhook events that matter, and the channel performance you can expect once it's wired up.

Key Takeaways

  • Lemon Squeezy's checkout on lemonsqueezy.com breaks standard cookie tracking for 100% of purchases — the first-party cookie never crosses the domain boundary by browser design.
  • Pass the visitor ID via checkout[custom][vid] in the checkout URL at click time, then read it from the order_created webhook to match every sale to its originating channel.
  • Always attribute on order_created (completed paid order) — not order_refunded — and keep the buyer email as a fallback match for cookie-blocked visitors.
  • Use subtotal not total when comparing channel ROI — the total field includes EU/UK VAT and inflates apparent revenue for buyers in those regions.
  • ~60% of Lemon Squeezy sellers cannot name which channel pays their MRR; the custom-data passthrough is a one-time fix that closes this channel-attribution gap permanently.

Why This Matters for Your Revenue

100% of Lemon Squeezy purchases are unattributed without the workaround — every dollar of LS revenue lands in the (direct) bucket unless the session ID is explicitly threaded across the cross-domain redirect.

For a maker doing $10K/month on Lemon Squeezy, that is the entire marketing budget being allocated on guesses: the newsletter that quietly drives half the LTV looks identical to the Twitter thread that drove none, and the channel that gets cut next is chosen by intuition rather than data.

The compounding cost is the channels you scale on phantom signal and the ones you starve because the dashboard refuses to credit them.

As a Merchant of Record, Lemon Squeezy already takes around 5% + 50¢ of every sale to handle global tax and compliance — so misallocating the marketing spend on top of that fee compounds two costs at once.

The fix is mechanical and one-time.

Once the checkout[custom][vid] handshake is in place, every Lemon Squeezy order — first sale, renewal, refund — carries its originating channel through the webhook, and revenue per click becomes a number you can actually compare across sources.

Teams that wire this in correctly typically recover the cost of the tooling within the first month, purely by reallocating spend away from one channel the old reports had flattered.

The same plumbing pattern shows up across providers — see our guide to Stripe revenue attribution for the on-site equivalent.

Why Lemon Squeezy Attribution Is Harder Than Stripe

The entire difficulty comes down to one fact: Lemon Squeezy's checkout is cross-domain. When a visitor clicks "Buy now" on your site, the browser redirects them away from yourbrand.com and onto lemonsqueezy.com (or your store's *.lemonsqueezy.com subdomain).

Any first-party cookie you set on your own domain — the cookie holding the session ID, the UTM source, the whole click context — does not travel across that domain boundary.

The browser scopes first-party cookies to the origin that set them, so by the time the buyer is on Lemon Squeezy's payment page, the tracking state has been left behind on your site.

Stripe avoids this in the common case. Most Stripe integrations use on-site Stripe Elements (no domain change at all) or set client_reference_id on the Checkout Session before redirecting, so the identifier is carried server-side rather than via a cookie.

Lemon Squeezy gives you no on-site embed equivalent — every single purchase is a cross-domain redirect, every time.

That means you cannot treat attribution as an afterthought you bolt on later; the session ID has to be threaded into the checkout URL from the very first sale, or the click context is gone for good.

The mechanism that closes this gap is the same custom-data passthrough we walk through below, applied to 100% of checkouts rather than the occasional edge case.

For a focused walkthrough of the domain boundary itself, see our guide to solving the Lemon Squeezy checkout cross-domain tracking problem.

The Lemon Squeezy attribution gap

Lemon Squeezy tracks your sales beautifully. It does not track where those buyers came from. The default dashboard shows revenue by product, by plan, by country — never by acquisition source.

Worse, the checkout flow happens on a Lemon Squeezy-controlled domain (store.lemonsqueezy.com or your store's subdomain), which is cross-domain from your marketing site. A first-party cookie set on yourbrand.com doesn't travel to lemonsqueezy.com.

By the time the buyer pays, you've lost the click context unless you explicitly forwarded it.

The bridge: custom data passthrough

Stripe Checkout has the same cross-domain problem in theory, but most teams use Stripe with on-site Elements (no domain change) or use the client_reference_id field on Checkout Sessions.

Lemon Squeezy is checkout-only — every purchase is cross-domain — so the workaround has to be built in from day one.

The path: capture a session ID at landing time on your domain, embed it in the Lemon Squeezy checkout URL as a custom field, and read it back from the webhook payload.

The mechanism is identical to UTM passthrough, but every checkout flows through it.

The three-step setup

Step 1 — Add a tracking link per channel. Same as any attribution setup. Newsletter, YouTube description, partner referrals, each gets its own link with UTM values baked in.

Step 2 — Capture a session ID on the landing page and pass it as a custom field on the Lemon Squeezy checkout URL. Lemon Squeezy accepts ?checkout[custom][key]=value URL parameters that flow through to the order.

Step 3 — Listen to the Lemon Squeezy webhook and read the custom field from the payload. Match it back to your click record.

The temptation at indie scale is to share one Lemon Squeezy buy-link everywhere — in the newsletter, the YouTube description, the Twitter bio.

That collapses every channel into a single undifferentiated bucket, and attribution is impossible no matter how clean the rest of the pipe is.

A unique tracking link per channel writes a distinct UTM source into the cookie at landing time, which is the value you ultimately credit the sale to.

Without distinct links there is nothing to attribute; the session ID still flows through, but every sale points back to the same anonymous source.

Test mode vs live mode checkouts

Lemon Squeezy test-mode checkout URLs use a different store identifier than live mode but flow through the same custom-data parameter mechanism.

Build the parameter the same way in both environments — the webhook payload includes a test_mode: true flag so your attribution store can ignore test orders without losing the wiring.

Forgetting to filter test orders is the most common reason a live channel-revenue chart shows phantom $0.50 transactions.

The session ID has to be stored in a cookie set on your own domain — a first-party cookie — not a third-party one.

Safari's Intelligent Tracking Prevention and iOS privacy controls aggressively block or cap third-party cookies, so a third-party session ID can be gone before the visitor ever reaches checkout.

A first-party cookie set on yourbrand.com survives long enough to be read at click time and forwarded into the Lemon Squeezy URL, which is the whole point of capturing it on your domain first.

If your marketing site lives on yourbrand.com but your app lives on app.yourbrand.com, set the cookie with Domain=.yourbrand.com (leading dot) so both surfaces read the same visitor ID.

Without the leading dot, the cookie is scoped to the exact origin only, and a visitor who lands on marketing then signs up on app starts a brand-new session — splitting the attribution chain in two.

Passing the session ID through checkout

Lemon Squeezy checkout URLs support arbitrary custom data via the checkout[custom][key]=value syntax. Your tracking pixel reads the visitor ID from the first-party cookie, then your "Buy now" link writes it into the checkout URL.

The visitor ID has to be read from the cookie at the exact moment the buyer clicks "Buy now" — not cached earlier in a variable on page load.

On a single-page app the same page can stay open for minutes while the pixel finishes binding the session, and a value captured too early may be stale or empty.

Reading document.cookie at click time guarantees you forward the final, settled visitor ID into the checkout URL.

Why the custom key name matters

The key you choose inside checkout[custom][...] is the same key you read back in the webhook — they have to match exactly.

Pick a stable, namespaced key like vid and never rename it, because Lemon Squeezy returns whatever key you sent, unchanged, and a typo on either side silently produces an empty attribution.

Keep the value short: a single session ID, not a JSON blob, since URL-encoded payloads are easy to truncate.

Client: build the checkout URL with session ID
// Read the first-party visitor cookie set by TrackRev's pixelconst vid = document.cookie  .split("; ")  .find((row) => row.startsWith("vid="))  ?.split("=")[1]; // Append it to the Lemon Squeezy checkout URL as a custom fieldconst checkoutUrl = new URL("https://yourstore.lemonsqueezy.com/checkout/buy/abc123");if (vid) {  checkoutUrl.searchParams.set("checkout[custom][vid]", vid);} window.location.href = checkoutUrl.toString();

Reading the session ID from the Lemon Squeezy webhook

On the server side, listen for order_created (and subscription_created for recurring). The custom field shows up under attributes.first_order_item.custom_data or meta.custom_data depending on the webhook event.

Webhook handler: read custom data from Lemon Squeezy
// POST /api/lemonsqueezy/webhookconst event = JSON.parse(rawBody); if (event.meta?.event_name === "order_created") {  const vid = event.meta?.custom_data?.vid;  const orderEmail = event.data?.attributes?.user_email;  const orderTotal = event.data?.attributes?.total / 100; // cents to dollars   // attribute the order: find the click record with this vid,  // walk back to the originating channel  attributeOrder({ vid, email: orderEmail, revenue: orderTotal });}

Why order_created is the right event to listen for

Lemon Squeezy fires several webhook events across an order's lifecycle, and only one of them represents a completed, paid sale: order_created. Attaching attribution to any other event corrupts your numbers.

order_refunded fires when money goes back to the customer — attributing on it would credit a channel for revenue you no longer have. subscription_payment_success is correct for recurring renewals, but for the first conversion order_created is the authoritative signal.

Treat order_created as the moment to write the attribution record, and handle refund and renewal events separately to keep LTV per channel accurate.

Verifying the X-Signature header

Lemon Squeezy signs every webhook with an HMAC-SHA256 hash of the raw body, sent in the X-Signature header, and the official Lemon Squeezy signing-requests guide shows the exact comparison to run.

Verify it against the signing secret you set in the dashboard before parsing the payload. A handler that skips verification accepts spoofed orders, which can poison your channel revenue with attacker-supplied vid values and inflate certain channels arbitrarily.

Retry behaviour on webhook failure

Lemon Squeezy retries failed webhook deliveries for up to 72 hours with exponential backoff. Make your handler idempotent on the event_id: store the ID on first success and treat any duplicate as a no-op.

Otherwise a transient 500 followed by a retry will double-count the conversion against the originating channel.

Why you still match on email as a fallback

The custom-data passthrough is the primary key, but a small fraction of buyers will arrive with no session ID — they cleared cookies, used a privacy browser that blocked the pixel before redirect, or pasted the raw checkout link from somewhere.

For those orders, the buyer's email in the webhook payload is your fallback anchor: if that email already exists against a known visitor record from an earlier session, you can still recover the channel.

Matching on both the session ID and the email closes most of the gap that pure cookie-based attribution leaves open.

Lemon Squeezy vs Stripe vs Paddle attribution complexity

Three checkout flows, three different mechanisms for passing data through. Below is the comparison.

AspectStripeLemon SqueezyPaddle
Checkout domainYour domain (Elements) or stripe.comlemonsqueezy.comcheckout.paddle.com
Cookie trackingWorks natively on ElementsRequires URL parameterRequires URL parameter
Webhook availability
Custom metadata support✓ (metadata)✓ (custom_data)✓ (passthrough)
Setup complexityLow (Elements) / Medium (Checkout)MediumMedium
Recurring + refunds in webhook
Merchant of Record (handles tax/VAT)✗ (PSP — tax is on you)✓ (~5% + 50¢)✓ (~5% + 50¢)

What channel revenue looks like for Lemon Squeezy products

Based on attribution data across TrackRev workspaces using Lemon Squeezy, the channel mix tilts more toward direct, newsletter, and product launch traffic — Lemon Squeezy's audience skews indie/maker, which means owned distribution dominates.

ChannelAvg. click-to-paid rateAvg. revenue per clickNotes
Newsletter5.1%$4.20Higher than SaaS avg — indie audiences buy
Product Hunt3.8%$2.90Spike pattern — launch day dominant
Twitter/X organic1.4%$1.20Audience-dependent
Affiliate4.2%$5.10Indie creators run efficient programs
Direct7.2%$6.40Strong word-of-mouth + brand

Source: Aggregate TrackRev workspace data, 2026. Lemon Squeezy-connected workspaces only.

Common Lemon Squeezy Attribution Mistakes

Three mistakes account for almost every broken Lemon Squeezy attribution setup. All three produce the same symptom — sales landing in the (direct) bucket — and all three are quick to fix once you know the failure mode.

Mistake 1: Not passing the session ID as a custom parameter in the checkout URL. This is the most common one.

Teams set the first-party cookie correctly on their site, then link straight to the bare Lemon Squeezy checkout URL without appending ?checkout[custom][vid]=....

The cookie never makes the cross-domain trip, the webhook arrives with an empty custom_data, and there is nothing to match the order against. Always build the checkout URL dynamically and append the session ID at click time.

Mistake 2: Reading the wrong webhook event. Listening for order_refunded instead of order_created credits a channel at the moment money leaves your account rather than when a sale completes.

The result is negative or phantom attribution that makes good channels look broken. order_created is the event that represents a completed, paid order — attribute on that, and handle refunds as a separate adjustment.

Mistake 3: Using a third-party cookie instead of a first-party one. If the session ID is stored in a third-party cookie, Safari's Intelligent Tracking Prevention can block or expire it before the buyer ever clicks "Buy now" — meaning the value is already gone at the point of the checkout redirect.

A first-party cookie set on your own domain survives ITP's restrictions long enough to be read and forwarded. This is the same first-party requirement that makes link tracking survive iOS privacy controls.

Watch out

Lemon Squeezy's total field is already net of EU/UK VAT but gross of the Lemon Squeezy fee. Always attribute on subtotal (or compute total - tax) when comparing channel ROI, or VAT-heavy regions will look more profitable than they are. TrackRev's Lemon Squeezy integration handles the tax split automatically.

TrackRev and Lemon Squeezy attribution

TrackRev natively supports Lemon Squeezy.

Paste your Lemon Squeezy API key into TrackRev's integration settings, drop the pixel on your site, and the checkout URL builder is handled automatically — the session ID is appended to every Lemon Squeezy purchase link without manual code changes.

Related reading: how to attribute Stripe revenue to marketing channels covers the simpler Stripe-side flow; Paddle revenue attribution covers the equivalent Paddle setup. TrackRev's free tier covers 1,000 events.

External references: Lemon Squeezy API documentation; Lemon Squeezy webhook events; Stripe documentation for the comparison.

Once Lemon Squeezy attribution is working, the next question most teams hit is what to do about affiliates — because the standard Lemon Squeezy maker stack pairs a link tracker like Bitly Growth at $35 a month with a separate affiliate platform like Rewardful Starter at $49 a month, for $84 a month combined.

The two systems hold their own definitions of a conversion, fire against their own copies of the Lemon Squeezy webhook, and force a monthly reconciliation to keep the dashboards honest.

TrackRev covers both jobs for $39 a month against a single Lemon Squeezy connection with one definition of a sale — 54% cheaper than the two-tool stack and free of the reconciliation step.

If you are evaluating attribution for a Lemon Squeezy product today, the lower-cost answer is the same one that handles your affiliate program tomorrow, on the same session-ID handshake you have just wired up.

Lemon Squeezy attribution and a Lemon Squeezy affiliate programme are the same plumbing problem with the same answer — one tool reading one Stripe-style webhook with one definition of a sale.

Most SaaS teams run Bitly Growth ($35/mo) for link tracking and Rewardful Starter ($49/mo) for affiliates — $84/mo for two tools with two different definitions of a conversion.

TrackRev is $39/mo for both, on the same Lemon Squeezy data, with no monthly reconciliation between systems.

The checkout[custom][vid] handshake you just wired up to attribute a newsletter sale is the exact same handshake that credits an affiliate when their link drives the same kind of purchase — running them as two tools just doubles the cost and the reconciliation work.

If you sell on Lemon Squeezy, the simplest setup is the cheapest one.

Found this useful? Share it.

PostLinkedIn

Frequently asked questions

Muzahid Maruf — Founder of TrackRev.io

Written by

Muzahid Maruf, Founder, TrackRev.io & Contant.io

Muzahid Maruf is the founder of TrackRev.io and Contant.io. He writes about marketing attribution, link tracking, and revenue analytics for SaaS teams.

Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth

Keep reading

Related articles from the TrackRev blog.

Stop guessing where your revenue comes from.

Set up TrackRev in 5 minutes. Free tier covers 1,000 events / month — no card needed.

Lemon Squeezy Revenue Attribution by Channel · TrackRev