Lemonsqueezy
Accept payments with Lemon Squeezy as merchant of record. Use this skill when
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: 202 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
Adyen
Accept payments with Adyen. Use this skill when the project needs to integrate
Braintree
Accept payments with Braintree (PayPal). Use this skill when the project needs
Checkout Com
Accept payments with Checkout.com. Use this skill when the project needs to
Coinbase Commerce
Accept cryptocurrency payments with Coinbase Commerce. Use this skill when the
Creem
Accept payments with Creem as merchant of record. Use this skill when the project
Klarna
Accept payments with Klarna. Use this skill when the project needs to integrate