How to Track Lemon Squeezy Revenue Back to the Marketing Channel That Earned It
Lemon Squeezy's checkout on lemonsqueezy.com breaks standard cookie tracking for 100% of purchases. The exact session ID passthrough setup that attributes every LS sale to its source.
Muzahid Maruf, Founder

How to Track Lemon Squeezy Revenue Back to the Marketing Channel That Earned It
Lemon Squeezy's checkout on lemonsqueezy.com breaks standard cookie tracking for 100% of purchases. The exact session ID passthrough setup that attributes every LS sale to its source.
Lemon Squeezy's checkout happens on lemonsqueezy.com — a different domain from your site — which means standard first-party cookies are left behind when the visitor clicks Buy Now, breaking 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 takeaway
Lemon Squeezy's checkout[custom][vid] URL parameter is the only supported bridge across the cross-domain checkout. Pass the TrackRev visitor ID in at click time, read it back from order_created, and every sale — including renewals — maps to the original marketing channel. Connect Lemon Squeezy to TrackRev to skip the manual wiring.
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.
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.
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.
Why a separate link per channel beats one shared link
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.
Why the session ID lives in a first-party cookie
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.
Cookie domain scope for subdomains
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.
Why read the cookie at click time, not page load
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.
// Read the first-party visitor cookie set by TrackRev's pixel
const vid = document.cookie
.split("; ")
.find((row) => row.startsWith("vid="))
?.split("=")[1];
// Append it to the Lemon Squeezy checkout URL as a custom field
const 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.
// POST /api/lemonsqueezy/webhook
const 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. 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.
| Aspect | Stripe | Lemon Squeezy | Paddle |
|---|---|---|---|
| Checkout domain | Your domain (Elements) or stripe.com | lemonsqueezy.com | checkout.paddle.com |
| Cookie tracking | Works natively on Elements | Requires URL parameter | Requires URL parameter |
| Webhook availability | ✓ | ✓ | ✓ |
| Custom metadata support | ✓ (metadata) | ✓ (custom_data) | ✓ (passthrough) |
| Setup complexity | Low (Elements) / Medium (Checkout) | Medium | Medium |
| Recurring + refunds in webhook | ✓ | ✓ | ✓ |
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.
| Channel | Avg. click-to-paid rate | Avg. revenue per click | Notes |
|---|---|---|---|
| Newsletter | 5.1% | $4.20 | Higher than SaaS avg — indie audiences buy |
| Product Hunt | 3.8% | $2.90 | Spike pattern — launch day dominant |
| Twitter/X organic | 1.4% | $1.20 | Audience-dependent |
| Affiliate | 4.2% | $5.10 | Indie creators run efficient programs |
| Direct | 7.2% | $6.40 | Strong 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.
Frequently asked questions
- How do I track which marketing channel drives my Lemon Squeezy sales?
- Lemon Squeezy's checkout happens on the lemonsqueezy.com domain, which means standard first-party cookies set on your site are not carried into the checkout. To attribute sales to channels, you need to: (1) capture a session ID on your landing page, (2) pass that session ID as a custom URL parameter in the Lemon Squeezy checkout link, and (3) read the session ID from the Lemon Squeezy webhook payload when the order is created and match it to your click record.
- Does Lemon Squeezy support UTM tracking natively?
- Lemon Squeezy does not natively parse or store UTM parameters. You need to capture UTM values on your own site when the visitor lands, store them against a session ID in a first-party cookie or your database, and then pass the session ID through the Lemon Squeezy checkout using the custom data parameter.
- What is the Lemon Squeezy custom data parameter?
- Lemon Squeezy allows you to pass a custom string to the checkout URL using the checkout[custom][key]=value parameter format. This string is returned unchanged in the order.created webhook payload. You can use this to pass a session ID, UTM source, or any other identifier you need for attribution.
- Can TrackRev track Lemon Squeezy revenue attribution automatically?
- Yes. TrackRev integrates natively with Lemon Squeezy. After installing the TrackRev pixel on your site and connecting your Lemon Squeezy store, TrackRev handles the session ID passing and webhook matching automatically. You see every Lemon Squeezy order attributed to the channel that drove it in the same dashboard as your Stripe, Paddle, and Polar revenue.