Selling software licenses on Lemon Squeezy
Selling software licenses on Lemon Squeezy
Connect a Lemon Squeezy store to LicenseSeat and orders for linked variants issue licenses; eligible email delivery sends them to the buyer. A refund revokes it. Subscriptions are supported: each renewal extends the license, a refunded renewal revokes it. Nothing to install: the connection is a webhook you create in your Lemon Squeezy dashboard.
Setup takes about five minutes.
Before you start
Link the product to a license plan before testing. Automatic license emails require an eligible LicenseSeat plan, Settings → Organization → Email license keys to my customers enabled, and a valid buyer email. Issuance can succeed without sending an email. Use a separate test organization when you need complete separation: connections in one organization share product mappings by provider.
Read Email delivery, subscription expiry and event recovery for the common prerequisites and recovery procedure. Setup instructions remain available on the connection page after saving.
1. Create the connection in LicenseSeat
In the LicenseSeat dashboard sidebar open Connected platforms, click Connect Your First Platform (or Connect Another), choose Lemon Squeezy, and give the connection a name. The page shows two values:
- your webhook URL
- your signing secret, which LicenseSeat generated for you
Copy both. Keep them private: anyone holding the secret could forge orders and mint licenses for your products.
2. Turn Test Mode off
Look at the bottom of Lemon Squeezy's left sidebar for the Test Mode switch. Make sure it is off before the next step.
Lemon Squeezy keeps test-mode and live-mode webhooks apart: a webhook created while Test Mode is on is only triggered by test orders, and one created with it off is only triggered by real orders. New stores start in Test Mode, so this is easy to miss. If you want a rehearsal as well, come back later and create a second webhook with Test Mode on (see step 5).
3. Create the webhook in Lemon Squeezy
Go to Settings → Webhooks and click the + button at the top right. Fill in:
| Field | Value |
|---|---|
| Callback URL | your LicenseSeat webhook URL from step 1 |
| Signing secret | your LicenseSeat signing secret from step 1 |
| Events | tick order_created and order_refunded |
If you sell subscriptions, also tick subscription_created, subscription_payment_success and subscription_payment_refunded.
Save. Lemon Squeezy accepts any secret of 6 to 40 characters; the one LicenseSeat generated is 40, so it fits as is.
order_created fires when an order is placed and that is what issues the license. order_refunded fires when you refund the order, fully or partially, and that revokes it. For subscriptions, subscription_payment_success arrives on every renewal and extends the license; subscription_payment_refunded revokes it. Every other event is ignored, so ticking more does no harm and unticking these does.
4. Link your products
Tell LicenseSeat which license plan to issue for each Lemon Squeezy variant. On the connection page click Link Product, pick the plan, and enter the Variant ID.
To find it, open Store → Products in Lemon Squeezy, open the product's menu and choose Copy ID. If the product has several variants (say a personal and a studio edition) and each should issue a different license, use the variant's own Copy ID instead. A product with a single price still has exactly one variant, which Lemon Squeezy calls Default, and Copy ID gives you its number.
The Variant ID is a plain number, for example 123456. A long code with dashes taken from a checkout link is the variant's checkout slug, not its ID, and will not match. Test Mode products have their own IDs, separate from live ones: link the live ID for real sales.
5. Test it
Lemon Squeezy can only simulate events for Test Mode orders, and Test Mode webhooks are separate from live ones, so a rehearsal needs its own setup:
- Turn Test Mode on.
- Create a second webhook exactly like step 3 (same URL, same secret, same events).
- Publish a product in Test Mode (test-mode products are separate copies) and link its Variant ID in LicenseSeat too.
- Open the product's Share or Preview checkout and buy it with card number
4242 4242 4242 4242, any future expiry and any CVC.
Within a few seconds the LicenseSeat connection shows Active and a license appears under your product, owned by the email you typed at checkout.
To replay an event without buying again, open the test order in Lemon Squeezy, click Simulate event, pick order_created or order_refunded from the dropdown and click Trigger event. On a test subscription the same button offers subscription_created, subscription_cancelled, subscription_expired and the rest; renewal events (subscription_payment_*) can only be simulated once a real renewal has happened, so use a daily billing interval on the test product if you need them.
Every delivery, live or test, is listed under Recent deliveries on Settings → Webhooks, with the response code LicenseSeat returned and a Resend button.
Revoke the test licenses afterwards, or refund the test order.
What happens on each event
| Lemon Squeezy event | LicenseSeat |
|---|---|
order_created |
Issues a license for the order's first item if its variant is linked, owned by the buyer's email, and emails the key. One license per order. |
order_refunded |
Revokes the license issued for that order. Partial refunds revoke too. |
subscription_created |
Sent together with order_created on the first payment of a subscription. LicenseSeat issues one license, not two, and remembers the subscription. |
subscription_payment_success |
On a renewal (billing_reason is renewal), extends the license issued when the subscription started. The initial payment is ignored here because order_created already handled it. |
subscription_payment_refunded |
Revokes the license attached to that subscription. |
subscription_cancelled, subscription_expired, subscription_updated, anything else |
Ignored. A cancelled subscription is not renewed again; its license expires at its stored expiry. These events do not revoke immediately. |
Good to know
-
Existing perpetual mappings. Subscription purchases still issue on a perpetual plan. Events that identify a subscription show a warning; cancellation does not revoke lifetime access. Existing perpetual licenses remain perpetual on renewal. Choose a finite plan for access that should expire, and review subscription access and recovery before changing existing mappings.
-
Finite subscription plans. Use a plan duration matching the billing interval. Initial purchases and free trials grant that duration; trial-end synchronization is not provided. Renewals extend to the invoice creation time plus the mapped plan duration, rather than importing the provider’s next billing date. Cancellation or a failed renewal leaves the stored expiry unchanged. Verify these dates in your rehearsal.
-
First item only. The order webhook names the first item; LicenseSeat uses its quantity when present and otherwise defaults to one. A subscription-first purchase defaults to one. Use separate orders with quantity one for predictable subscription fulfillment; later cart items are not fulfilled.
-
Renewals need history. A renewal only extends a license whose first purchase LicenseSeat already saw. Subscriptions that started before you connected cannot be matched automatically. A manual license with the same email still lacks subscription provenance; replay the initial purchase/subscription events or maintain access manually.
-
Signatures. Lemon Squeezy signs every request with your secret (HMAC-SHA256 of the raw body, sent in the
X-Signatureheader). LicenseSeat rejects anything else with401. -
Retries. If LicenseSeat does not answer
200, Lemon Squeezy retries up to three more times with growing delays (about 5, 25 and 125 seconds), then gives up; you can still Resend from Recent deliveries. LicenseSeat answers immediately and processes the event in the background, and duplicate deliveries of the same event are recognised, so an order is never fulfilled twice. -
Test orders. Orders placed in Test Mode carry
test_mode: trueand are treated like any other order once the test webhook exists. Revoke those licenses afterwards. -
Custom checkout data. You do not need to pass anything through
checkout[custom][...]: the order already names the variant, and that is what LicenseSeat matches on. -
Changing providers. A new Stripe connection does not migrate Lemon Squeezy subscription provenance. Plan a separate migration and test renewals/refunds before switching.
Troubleshooting
- The connection stays "Incomplete". No product is linked yet. Click Link Product on the connection page.
- Real orders never arrive, test orders do. The webhook was created while Test Mode was on. Turn Test Mode off and create another webhook with the same URL, secret and events.
- The event shows as "ignored" with "no active mapping". The order's variant ID does not match any linked product. Use Copy ID on the exact variant sold, and check you linked the live ID rather than a Test Mode one.
- Recent deliveries shows
401. The signing secret in Lemon Squeezy does not match. Open the webhook there, paste the secret from the LicenseSeat connection page again, and Resend the delivery. - A refund did not revoke. The refund must be issued on the order (or subscription invoice) that issued the license. Refunds of orders placed before the connection existed cannot be matched.
- A renewal did not extend. Either
subscription_payment_successis not ticked on the webhook, or the subscription started before the connection existed.
References
These are Lemon Squeezy's own pages. If a label or menu in the Lemon Squeezy dashboard ever differs from this guide, they are the source of truth:
- Creating webhooks — Settings » Webhooks, the three elements (Callback URL, Signing secret, List of events), the 6-40 character secret rule, and the delivery log with resend.
- Sync with webhooks (developer guide) — the dashboard steps ("click the plus icon"), the
meta/dataenvelope,meta.custom_data, retries, and the "Simulate event" option on test-mode subscriptions. - Webhook requests — the
Content-Type,X-Event-NameandX-Signatureheaders, the retry schedule, and a fullorder_createdpayload. - Signing requests —
X-Signatureas an HMAC-SHA256 hex digest of the raw body, with code samples. - Event types — every event name with its description, which object each carries, and the recommended minimum set.
- Example payloads —
order_created,subscription_created,subscription_cancelled,subscription_paused. - Simulate webhook events — which events can be simulated, and the rule that test and live webhooks are kept separate.
- Test mode — the Test Mode toggle, test card numbers, "Copy to Live Mode".
- Taking payments (developer guide) — "Variant IDs can be copied from product dropdown menus, using the 'Copy ID' option".
- Variants and the Variant object — the single "Default" variant rule.
- The Order object —
first_order_item.variant_id,user_email,total,status,refunded. - The Subscription invoice object —
billing_reason(initial,renewal,updated),subscription_id. - Passing custom data —
checkout[custom][...]and where it lands (meta.custom_data). - API changelog — no webhook deprecations as of July 2026.
- Stripe acquires Lemon Squeezy (July 2024) and 2026 update: Stripe Managed Payments (January 2026).