Lemonsqueezy
Accept payments with Lemon Squeezy as merchant of record. Use this skill when the project needs to integrate Lemon Squeezy for SaaS subscriptions, digital product sales, license key management, checkout overlay, tax compliance, or affiliate programs. Covers the Lemon Squeezy API, checkout, subscriptions, license keys, webhooks, and Lemon.js.
You are a payments specialist who integrates Lemon Squeezy into projects. Lemon Squeezy is a merchant of record that handles payments, tax, subscriptions, and license keys. Popular with indie developers and SaaS products for its simplicity. ## Key Points - Use `custom` data in checkouts to pass your userId for webhook correlation - Always verify webhook signatures with HMAC - Use the customer portal URL for subscription self-management - Use variants for different pricing tiers (monthly/annual, plans) - Test with test mode before going live - Use license keys for desktop/plugin software distribution - Store Lemon Squeezy customer IDs and subscription IDs in your database - Granting access on checkout redirect without webhook verification - Not storing the subscription ID — can't manage subscriptions later - Immediately revoking access on cancellation instead of using `ends_at` - Building custom billing UI when the customer portal handles it - Not handling `subscription_payment_failed` — silent access loss ## Quick Example ```bash npm install @lemonsqueezy/lemonsqueezy.js ``` ```typescript // Programmatic overlay window.createLemonSqueezy(); window.LemonSqueezy.Url.Open(checkoutUrl); ```
skilldb get payment-services-skills/lemonsqueezyFull skill: 201 linesLemon Squeezy Payment Integration
You are a payments specialist who integrates Lemon Squeezy into projects. Lemon Squeezy is a merchant of record that handles payments, tax, subscriptions, and license keys. Popular with indie developers and SaaS products for its simplicity.
Core Philosophy
Built for digital products
Lemon Squeezy is designed for SaaS, digital downloads, and software licenses. It handles the merchant of record responsibilities (tax, invoicing, compliance) so you focus on building product.
License keys for software distribution
Unlike Stripe or Paddle, Lemon Squeezy has first-class license key generation and validation. If you sell desktop apps, plugins, or self-hosted software, license management is built in.
Simple pricing, simple API
The API surface is intentionally smaller than Stripe. Products, variants (plans), checkouts, subscriptions, and webhooks. That's most of what you need.
Setup
Install
npm install @lemonsqueezy/lemonsqueezy.js
Initialize
import { lemonSqueezySetup, getAuthenticatedUser } from '@lemonsqueezy/lemonsqueezy.js';
lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY });
// Verify setup
const { data } = await getAuthenticatedUser();
console.log('Authenticated as:', data.data.attributes.name);
Key Techniques
Create checkout
import { createCheckout } from '@lemonsqueezy/lemonsqueezy.js';
const { data } = await createCheckout(storeId, variantId, {
checkoutData: {
email: 'user@example.com',
custom: { userId: '123' },
},
productOptions: {
redirectUrl: 'https://yourdomain.com/success',
},
});
const checkoutUrl = data.data.attributes.url;
// Redirect user to checkoutUrl
Overlay checkout (Lemon.js)
<script src="https://app.lemonsqueezy.com/js/lemon.js" defer></script>
<a href="https://yourdomain.lemonsqueezy.com/checkout/buy/variant-id"
class="lemonsqueezy-button">
Subscribe
</a>
// Programmatic overlay
window.createLemonSqueezy();
window.LemonSqueezy.Url.Open(checkoutUrl);
Subscription management
import {
getSubscription,
updateSubscription,
cancelSubscription,
} from '@lemonsqueezy/lemonsqueezy.js';
// Get subscription details
const { data: sub } = await getSubscription(subscriptionId);
const status = sub.data.attributes.status; // active, past_due, cancelled, etc.
// Update subscription (change plan)
await updateSubscription(subscriptionId, {
variantId: newVariantId,
prorationBillingMode: 'prorated_immediately',
});
// Cancel subscription (at period end)
await cancelSubscription(subscriptionId);
// Resume canceled subscription (before period ends)
await updateSubscription(subscriptionId, { cancelled: false });
License key management
import { validateLicenseKey, activateLicenseKey, deactivateLicenseKey } from '@lemonsqueezy/lemonsqueezy.js';
// Validate a license key
const { data } = await validateLicenseKey(licenseKey);
const isValid = data.valid;
const activationLimit = data.meta.activation_limit;
// Activate license for a device
const activation = await activateLicenseKey(licenseKey, 'device-fingerprint');
// Deactivate
await deactivateLicenseKey(licenseKey, activation.data.id);
Customer portal
import { getSubscription } from '@lemonsqueezy/lemonsqueezy.js';
const { data: sub } = await getSubscription(subscriptionId);
const portalUrl = sub.data.attributes.urls.customer_portal;
// Redirect user to portalUrl
Webhook Processing
| Event | Action |
|---|---|
order_created | Grant access, store customer ID |
subscription_created | Set up subscription record |
subscription_updated | Update plan/status |
subscription_cancelled | Schedule access revocation |
subscription_resumed | Restore access |
subscription_expired | Revoke access |
subscription_payment_failed | Notify user |
license_key_created | Store license key mapping |
import crypto from 'crypto';
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('x-signature');
const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET;
const hmac = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
if (hmac !== signature) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
const eventName = event.meta.event_name;
const userId = event.meta.custom_data?.userId;
switch (eventName) {
case 'order_created':
await grantAccess(userId, event.data.attributes);
break;
case 'subscription_cancelled':
const endsAt = event.data.attributes.ends_at;
await scheduleRevocation(userId, endsAt);
break;
case 'subscription_payment_failed':
await notifyPaymentFailed(userId);
break;
}
return new Response('OK');
}
Best Practices
- Use
customdata in checkouts to pass your userId for webhook correlation - Always verify webhook signatures with HMAC
- Use the customer portal URL for subscription self-management
- Use variants for different pricing tiers (monthly/annual, plans)
- Test with test mode before going live
- Use license keys for desktop/plugin software distribution
- Store Lemon Squeezy customer IDs and subscription IDs in your database
Anti-Patterns
- Granting access on checkout redirect without webhook verification
- Not storing the subscription ID — can't manage subscriptions later
- Immediately revoking access on cancellation instead of using
ends_at - Building custom billing UI when the customer portal handles it
- Not handling
subscription_payment_failed— silent access loss - Using webhooks without signature verification
Install this skill directly: skilldb add payment-services-skills
Related Skills
Mollie
Accept payments with Mollie. Use this skill when the project needs to integrate Mollie for European payments, iDEAL, Bancontact, SEPA, Klarna, subscriptions, payment links, or multi-currency checkout. Covers the Mollie API, payments, subscriptions, mandates, refunds, and webhooks. Popular in Netherlands, Belgium, Germany, and across Europe.
Paddle
Accept payments with Paddle as merchant of record. Use this skill when the project needs to integrate Paddle for subscription billing, one-time payments, checkout overlay, tax compliance, invoicing, or global payment processing where Paddle handles tax, compliance, and payouts. Covers Paddle Billing API, checkout, subscriptions, transactions, webhooks, and Paddle.js.
Paypal
Accept payments with PayPal. Use this skill when the project needs to integrate PayPal for checkout, subscriptions, one-time payments, PayPal buttons, venmo, invoicing, payouts, or PayPal REST API. Covers the PayPal JavaScript SDK, REST API v2, Orders, Subscriptions, Webhooks, and Smart Payment Buttons.
Razorpay
Accept payments with Razorpay. Use this skill when the project needs to integrate Razorpay for payments in India and international markets, UPI, subscriptions, payment links, invoicing, route (split payments), or Razorpay Checkout. Covers the Razorpay API, Checkout.js, orders, subscriptions, webhooks, and UPI.
Square
Accept payments with Square. Use this skill when the project needs to integrate Square for online payments, in-person POS, invoicing, subscriptions, catalog management, or omnichannel commerce. Covers the Square API, Web Payments SDK, Orders, Subscriptions, Invoices, Catalog, and webhooks.
Stripe
Accept payments and manage subscriptions with Stripe. Use this skill when the project needs to integrate Stripe for one-time payments, recurring subscriptions, checkout sessions, payment intents, customer management, invoicing, webhook processing, billing portal, metered billing, or payment method management. Covers the Stripe API, Stripe.js, Checkout, Elements, Billing, and webhooks.