Skip to content

Revenue from Lemon Squeezy

If you sell through Lemon Squeezy, Nordva Launch can read one of your stores and report the same numbers it reports for Stripe: MRR, who started paying, who cancelled, what was collected, and in Contacts and Attribution, who each paying customer is and which source they came from.

Launch reads, for the one store you pick:

Lemon Squeezy objectWhat Launch keeps
CustomersEmail, name, country, when they became a customer
SubscriptionsStatus, product and variant name, price, renewal interval, when it started, trial end, when it ends
Subscription invoicesEvery subscription charge, the first one included: amount, currency, date, refunds
OrdersOne-time purchases: amount, currency, date, refunds
Prices, variants, the storeLooked up by id to work out MRR and to tell one-time orders from subscription orders

Launch never writes to Lemon Squeezy: no refunds, no cancellations, no changes to customers, subscriptions, products or webhooks. It never sees card data.

A Lemon Squeezy API key cannot be limited to reading, the way a Stripe restricted key can. Whoever holds the key can do what its owner can. So the promise is about Launch, not the key: every request Launch makes to Lemon Squeezy goes through one client that can only send GET, and our tests check every request the sync and the connect screen make. The key is encrypted at rest, is never shown again, never appears in a response, a log or an AI conversation, and is deleted when you disconnect.

  1. In Lemon Squeezy, open Settings → API (app.lemonsqueezy.com/settings/api).
  2. To try this with test data first, switch Test mode on in the Lemon Squeezy dashboard before making the key. A test-mode key only sees test-mode data; Launch shows the connection as test mode.
  3. Click + to add a key, name it Nordva Launch, and create it.
  4. Copy the key. Lemon Squeezy shows it once. It is a long string starting eyJ.

Lemon Squeezy API keys are valid for a year. When yours expires, Launch marks the connection as stopped and emails you; connect a new key and the data catches up.

Open Settings → Revenue sources, choose Lemon Squeezy, paste the key and click Find stores. Launch checks the key, lists the stores it can see and whether it is a test key, and stores nothing yet. Pick the store and click Connect store. Launch then reads one of each list it needs from that store; if Lemon Squeezy refuses one, you see which, and nothing is saved.

Connecting is only possible in the dashboard, behind your login. There is no API-key endpoint and no MCP tool for it.

The first import starts immediately and runs in the background.

Launch matches a paying customer to the visitor they came from by email: an email sent with identify, a form with data-nordva-identify, or the same email on your waitlist. That covers people who signed up or logged in first.

To also cover people who went straight from your site to checkout, the analytics script adds the visitor id to your Lemon Squeezy checkout links, and Lemon Squeezy sends it back in a webhook:

  1. Under Link purchases to visits on the Lemon Squeezy card, click Set up webhook. Launch shows a URL and a signing secret, once.
  2. In Lemon Squeezy, open Settings → Webhooks and click +.
  3. Paste the URL as the callback URL and the secret as the signing secret, tick order_created and subscription_created, and save.

Once a signed delivery arrives, the card shows when. Launch reads only four things from it: the store, the customer id, the customer’s email and name, and the visitor id. It verifies the X-Signature (HMAC-SHA256 of the body with your secret) before reading anything, and does not keep the rest of the payload.

How the visitor id gets to Lemon Squeezy: when a visitor who may carry an analytics id (see tracking modes) clicks a link to https://<your-store>.lemonsqueezy.com/checkout/... or /buy/..., the script adds checkout[custom][nv_vid]=<visitor id> to the link. Nothing is added for a visitor without an id. If you create checkouts on your server with Lemon Squeezy’s API instead, put the id there yourself:

// In the browser, before you ask your server for a checkout:
const visitor = window.nordva?.visitor?.();
// On your server, in the checkout you create:
checkout_data: { custom: { nv_vid: visitorIdFromTheBrowser } }

A checkout on a custom domain of your own is not changed by the script; use the server way above.

  • Money is before tax, after discounts. For each order and subscription invoice Launch stores total - tax. A partial refund scales it down by refunded_amount / total. Fully refunded, pending, failed, void and fraudulent charges are not payments; a charge refunded after it was imported is removed at the next sync.
  • No double counting. A subscription’s first payment appears in Lemon Squeezy both as an order and as the subscription’s first invoice. Launch counts subscription money from subscription invoices only, and orders only for one-time products.
  • MRR comes from each subscription’s price: unit price × quantity over the renewal interval (yearly ÷ 12, weekly × 52 ÷ 12), through the same maths as Stripe. Usage-based, graduated and volume prices count as zero, and so does a paused subscription. Lemon Squeezy does not expose a subscription’s discount through its API, so MRR is at the list price.
  • Currencies. Payments stay in the currency the buyer paid in; MRR is in the store’s currency. Nothing is converted.
  • Statuses use the same words as Stripe everywhere in Launch:
Lemon SqueezyIn Launch
on_trialtrialing (not in MRR)
activeactive
cancelledactive, cancelling at the end of the period (it is still paid up until ends_at)
expiredcanceled, ended at ends_at
past_due, unpaid, pausedthe same

With Stripe and Lemon Squeezy both connected, every figure adds both per currency, and sync.providers in each response lists the two connections. A person paying through both counts as two customers unless the same email links them in Contacts.

Lemon Squeezy has no change feed, so each hourly sync reads every subscription (their status is what changes), customers new since the last full import, and orders and invoices from 45 days before it (refunds land on older charges). The full import runs again every week, which picks up anything else, such as a changed email. A store too large to refresh within one sync’s request budget (about 2,500 subscriptions) falls back to a full import every run: the numbers stay right, but the status shows importing more often. The webhook above also makes the next sync start within a minute of a purchase.

The endpoints are the same as for Stripe. GET /v1/integrations/lemonsqueezy returns the connection’s status; the revenue endpoints include Lemon Squeezy rows, and GET /v1/revenue/events says which provider each row came from.

Terminal window
curl "https://api.nordva.dev/v1/integrations/lemonsqueezy" \
-H "Authorization: Bearer nv_live_YOUR_SECRET_KEY"
{
"data": {
"provider": "lemonsqueezy",
"connected": true,
"status": "active",
"livemode": false,
"store_name": "Lemonade Stand",
"last_synced_at": "2026-09-27T14:02:11.000Z",
"synced": { "customers": 214, "subscriptions": 61, "payments": 488 },
"webhook": { "configured": true, "last_received_at": "2026-09-27T13:58:40.000Z" }
}
}

From an MCP client, get_lemonsqueezy_connection_status reports the same, and the revenue tools read both providers. The assistant cannot connect Lemon Squeezy and will never ask you for a key.

Disconnect deletes the stored key, the webhook secret and every synced Lemon Squeezy customer, subscription and payment, and removes contacts who were only known from them. Stripe data, if connected, stays. Nothing changes in Lemon Squeezy: delete the API key and the webhook there as well. Deleting the project does the same.

CodeMeaning
LEMONSQUEEZY_KEY_INVALIDLemon Squeezy did not accept the key: expired, deleted or copied incompletely.
LEMONSQUEEZY_KEY_MISSING_ACCESSLemon Squeezy refused one of the reads. The message says which. Make the key as the store’s owner or an admin.
LEMONSQUEEZY_STORE_NOT_FOUNDThe store is not one this key can see. A test key only sees test data.
LEMONSQUEEZY_ALREADY_CONNECTEDThis project already has a store connected. Disconnect it first.
LEMONSQUEEZY_CONNECTION_LIMIT_REACHEDFree and Indie include one Lemon Squeezy store per account. Builder includes one per project.
STRIPE_NOT_CONNECTEDA revenue endpoint was called with neither Stripe nor Lemon Squeezy connected. The code name is older than Lemon Squeezy support.