Commet
  • Pricing
Log InTry out

Migrate from Lemon Squeezy to Commet

A practical guide to moving stores, subscriptions, and usage records from Lemon Squeezy to Commet — with side-by-side code for every step.


Introduction

Lemon Squeezy got you selling fast — hosted checkout, taxes handled, no compliance homework. But since the Stripe acquisition you've been watching the roadmap, your billing needs have outgrown variants and usage records, and the only official SDK is JavaScript.

Commet is also a Merchant of Record, so tax, compliance, refunds, and payouts stay off your plate. What you gain is a billing model built for SaaS and AI products: plans as the source of truth, native usage metering, and SDKs in five languages.

Jump ahead to any section:

  • History
  • Concepts
  • Official SDKs
  • Create customers & subscriptions via API
  • Track usage / metering
  • Webhooks
  • Taxes & compliance
  • Pricing
  • Conclusion

History

Lemon Squeezy launched in 2021 as a Merchant of Record for digital products — software licenses, downloads, memberships, and SaaS subscriptions — with a strong focus on indie developers who wanted to sell globally without touching tax compliance. In July 2024, Stripe acquired Lemon Squeezy, and its Merchant of Record capabilities began informing Stripe's own roadmap.

Commet applies the same Merchant of Record model to a specific segment: SaaS and AI products that charge for consumption. Plans define pricing and features, subscriptions bill automatically, and usage events drive metered, credit, or balance-based charges — with local-currency pricing for markets where USD checkout hurts conversion.

Because both platforms are Merchant of Record, tax coverage carries over unchanged — the comparison that matters is API surface, metering depth, and fees. That's what the rest of this guide covers.

Concepts

Both platforms are Merchant of Record, so the seller-of-record model carries over. The billing objects map like this:

Lemon SqueezyCommet
StoreYour organization
Product + VariantPlan (pricing, features, and intervals in one object)
Checkout (hosted URL)checkoutUrl returned by subscriptions.create
SubscriptionSubscription
Customer (created at checkout)Customer (created via API, idempotent)
Usage records on subscription itemsUsage events on metered features
DiscountsPromo codes
WebhooksWebhooks (optional — state is queryable)

The structural difference: in Lemon Squeezy, the checkout is the entry point and the customer materializes after purchase. In Commet, the customer and subscription are API objects first — checkout is a URL the API hands back.

Subscription statuses

The lifecycles translate cleanly:

Commet statusGrants access?Closest Lemon Squeezy status
pending_paymentNo— (checkout not yet completed)
trialingYeson_trial
activeYesactive
past_dueYes (grace period)past_due
pausedNopaused
canceledNocancelled
expiredNoexpired

Gate access on active or trialing, and treat past_due as a grace period while payment retries run.

Consumption models

Each Commet plan uses exactly one consumption model — they are mutually exclusive per plan:

  • metered: pay-per-use, aggregated and invoiced at period end. Maps to Lemon Squeezy's usage-based variants.
  • credits: customers buy credit packs upfront; usage draws them down.
  • balance: a prepaid monetary balance debited per event, including per-token AI pricing.

Credits and balance have no Lemon Squeezy equivalent — they're the models AI products reach for once flat metering isn't enough.

Official SDKs

Lemon Squeezy ships one official SDK, in JavaScript. Commet covers five languages plus framework integrations:

SDK / toolLemon SqueezyCommet
Node.js / TypeScript@lemonsqueezy/lemonsqueezy.js@commet/node
Python—commet
Go—commet-go
Java—commet-java
PHP—commet-php
Browser / checkoutLemon.js (overlay, optional)Not required — checkout is a redirect URL
Next.js helpers—@commet/next (webhook handler, route helpers)
AI SDK integration—commet-ai-sdk (token usage tracking)
Auth integration—commet-better-auth
CLI—commet CLI (commet listen for local webhooks)

Install the Node.js SDK:

npm install @commet/node

Initialize the client:

lib/commet.ts
import { Commet } from '@commet/node'

export const commet = new Commet({
  apiKey: process.env.COMMET_API_KEY!,
})

API keys are environment-scoped: ck_sandbox_xxx keys hit your sandbox organization, so the same code runs in sandbox and production with only the key changing.

Create customers & subscriptions via API

In Lemon Squeezy, you create a checkout for a store and variant; the customer and subscription come into existence when the buyer completes it:

Lemon Squeezy
import { lemonSqueezySetup, createCheckout } from '@lemonsqueezy/lemonsqueezy.js'

lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY! })

const checkout = await createCheckout(storeId, variantId, {
  checkoutData: {
    email: 'billing@acme.com',
    custom: { user_id: 'user_123' },
  },
})

// redirect to checkout.data?.data.attributes.url

Linking that purchase back to your user means round-tripping custom data through webhooks.

In Commet, you create the customer explicitly with your own ID, then the subscription — and the checkout URL comes back in the same call:

Commet
const customer = await commet.customers.create({
  email: 'billing@acme.com',
  id: 'user_123',
})

const subscription = await commet.subscriptions.create({
  customerId: 'user_123',
  planCode: 'pro',
})

// redirect to subscription.data.checkoutUrl

customers.create is idempotent — if a customer with the same id already exists, it returns the existing record. Your user ID works everywhere; no mapping table, no custom-data round trip.

The parameters subscriptions.create accepts:

ParameterTypeDescription
customerIdstringCommet customer ID (cus_xxx) or your external ID
planCodestringPlan code (alternative to planId)
planIdstringPlan UUID (alternative to planCode)
billingIntervalstringweekly, monthly, quarterly, yearly, or one_time
initialSeatsobjectSeat type codes mapped to quantities
skipTrialbooleanSkip the plan's trial period
successUrlstringRedirect URL after successful payment

Checking access is a direct query:

Commet
const sub = await commet.subscriptions.getActive({ customerId: 'user_123' })

if (sub.data?.status === 'active') {
  // User has paid
}

Plan changes and proration

Commet ships one default policy for plan changes: changes that benefit the customer apply immediately; changes that cost them apply at renewal.

Upgrades prorate and take effect right away; downgrades schedule for the next period. Customers can also change plans themselves through the Customer Portal — Commet's equivalent of Lemon Squeezy's customer portal, created automatically per customer.

Trials

Trials are defined on the plan, and skipTrial: true bypasses them per subscription. During a trial the card is captured but not charged, and Commet emits trial.started, trial.will_end (3 days before expiry), and trial.converted or trial.expired at the end.

Track usage / metering

Lemon Squeezy supports usage-based billing through usage records reported against a subscription item:

Lemon Squeezy
import { createUsageRecord } from '@lemonsqueezy/lemonsqueezy.js'

await createUsageRecord({
  quantity: 1,
  action: 'increment',
  subscriptionItemId: 123456,
})

Note the identifier: usage attaches to a subscription item ID, which you must look up and store per customer.

In Commet, usage attaches to the customer and a feature code you define — no intermediate IDs:

Commet
await commet.usage.track({
  customerId: 'user_123',
  feature: 'api_calls',
  value: 1,
  idempotencyKey: 'req_abc123',
})

The full parameter list:

ParameterTypeRequiredDescription
featurestringYesEvent code of a metered feature (a-z0-9_)
customerIdstringYesCommet customer ID or your external ID
valuenumberNoQuantity consumed. Defaults to 1
idempotencyKeystringNoPrevents duplicate events
timestampstringNoISO 8601 datetime. Defaults to now
propertiesobjectNoKey-value metadata for debugging

The idempotencyKey prevents duplicate events — Commet rejects events with a key already recorded for the same customer.

Entitlement checks run against the same plan definition, so you can gate in real time instead of discovering overage at invoice time:

Commet
const { data } = await commet.featureAccess.get({
  code: 'api_calls',
  customerId: 'user_123',
})

if (data.allowed) {
  // Serve the request
}

AI token billing

If you charge for AI usage, Commet maintains a catalog of 180+ model prices. Pass model, inputTokens, and outputTokens to the same track method, and Commet computes the cost, applies your configured margin, and debits the customer's balance:

Commet
await commet.usage.track({
  customerId: 'user_123',
  feature: 'ai_chat',
  model: 'gpt-4o',
  inputTokens: 1500,
  outputTokens: 300,
})

On Lemon Squeezy, this pipeline — model price lookup, margin, balance debit, overage invoicing — is code you write and keep current as model prices change.

See usage-based billing for how metered, credits, and balance compare.

Metered features can carry quotas and seat limits too: Commet emits quota.threshold_reached and quota.exceeded as customers approach their limits, and seats.updated / seats.limit_reached for seat-based features — signals you'd otherwise compute from your own usage tables.

Webhooks

Lemon Squeezy signs webhooks with HMAC-SHA256 in the X-Signature header, and verification is manual:

Lemon Squeezy
import crypto from 'node:crypto'

const digest = crypto
  .createHmac('sha256', process.env.LEMONSQUEEZY_WEBHOOK_SECRET!)
  .update(rawBody)
  .digest('hex')

if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))) {
  throw new Error('Invalid signature')
}

const event = JSON.parse(rawBody)

switch (event.meta.event_name) {
  case 'subscription_created':
    // Grant access
    break
  case 'subscription_cancelled':
    // Revoke access
    break
}

Commet verifies for you. In Next.js, @commet/next ships a typed handler:

app/api/webhooks/commet/route.ts
import { Webhooks } from '@commet/next'

export const POST = Webhooks({
  webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,

  onSubscriptionActivated: async (payload) => {
    // Grant access
  },

  onSubscriptionCanceled: async (payload) => {
    // Revoke access
  },
})

Outside Next.js, commet.webhooks.verifyAndParse({ rawBody, signature, secret }) does the HMAC check and returns the typed payload — the signature arrives in the X-Commet-Signature header.

The events you handle today map directly:

Lemon Squeezy eventCommet event
subscription_createdsubscription.created / subscription.activated
subscription_updatedsubscription.updated
subscription_plan_changedsubscription.plan_changed
subscription_cancelledsubscription.canceled
subscription_payment_successpayment.received
subscription_payment_failedpayment.failed
subscription_payment_recoveredpayment.recovered
order_refundedpayment.refunded

Operational details:

  • Failed deliveries retry with exponential backoff — 8 attempts, from 1 minute to 6 hours — then Commet emails you the endpoint, event, and last response.
  • For local development, commet listen localhost:3000/api/webhooks/commet forwards live events to your machine, no tunneling tools needed.
  • If three deliveries in a row fail, Commet auto-disables the endpoint and emails you a confirmation; re-enable it from Settings → Webhooks once your receiver is fixed.
  • Subscription state is queryable via subscriptions.getActive, so webhooks are notifications — not the mechanism that keeps your database correct.

Taxes & compliance

Both platforms are Merchant of Record: each sells to your customer, calculates and remits tax, and absorbs compliance. If Lemon Squeezy's tax coverage is why you signed up, you lose nothing by moving to Commet.

The differences are in market coverage and payment surface:

  • Local currencies in Latin America. Commet prices plans in ARS, BRL, CLP, COP, PEN, UYU, PYG, BOB, MXN, CAD, and EUR, with a canonical USD price and currency auto-detected from the customer's billing country at checkout. For LatAm customers, paying in local currency directly affects conversion and card approval rates.
  • Payment methods. Lemon Squeezy supports cards plus PayPal. Commet checkout is card-only today — if PayPal volume matters to your revenue, factor that into the migration.
  • Refunds and disputes are handled by the Merchant of Record on both platforms. On Commet, refunds are free and disputes cost $15.
  • Payouts are part of the Merchant of Record deal on both sides: the platform collects from your customers and pays out your net revenue.

Pricing is regional by design. You define one canonical USD price per plan, add local prices only for the currencies you care about, and internal accounting stays in USD.

Pricing

Both platforms charge a single all-in fee — processing, tax, and Merchant of Record liability included:

Lemon SqueezyCommet
Per successful transaction5% + $0.504.5% + $0.40
Payment processingIncludedIncluded
Tax calculation & remittanceIncludedIncluded
International cardsIncluded in base fee+1.5% (non-US cards)
DisputesPer Lemon Squeezy's current policy$15 per dispute
Refunds—Free

On $500K of annual card revenue at a $25 average transaction, the headline difference — 0.5% plus $0.10 per transaction — is $4,500 per year. The gap grows with transaction count, so run it against your own distribution.

Neither platform charges monthly platform fees — both are pure take-rate, so cost scales with revenue from the first transaction.

Full details on the pricing page.

Conclusion

Moving between Merchants of Record is a contained migration: your tax story doesn't change, and the work concentrates in the billing objects.

One thing does not migrate automatically, so plan for it: historical invoices and orders. Keep Lemon Squeezy access for past records; Commet generates invoices from the first migrated cycle forward.

Test the whole flow before touching production: every Commet account includes an isolated sandbox environment with test cards and a test clock, so you can simulate a full billing cycle — checkout, usage, renewal — in minutes.

  1. Model your Products and Variants as Commet plans with explicit features.
  2. Replace hosted checkout creation with customers.create + subscriptions.create → redirect to checkoutUrl.
  3. Swap usage records tied to subscription item IDs for commet.usage.track keyed by your own user ID.
  4. Port webhook handlers — @commet/next removes the manual HMAC verification.
  5. Cut over at renewal, running one cycle in parallel to compare invoices.

The payoff is concentrated in steps 2 and 3: your own user IDs flow through the whole billing system, and the subscription-item lookup tables disappear.

Start with the quickstart, or see how Commet compares to Stripe Billing and Paddle.

Developers

  • Documentation
  • Templates
  • GitHub

Frameworks

  • Next.js
  • Remix
  • Nuxt
  • SvelteKit
  • Astro
  • Express
  • Hono
  • Django
  • FastAPI

Resources

  • Blog
  • Changelog
  • Pricing

AI

  • Agents
  • MCP Server
  • Agent Skills
  • Claude Code
  • Codex
  • Cursor

Learn

  • Guides
  • Glossary
  • Solutions
  • Billing for AI Models
  • Comparison

Compare

  • Stripe alternative
  • Orb alternative
  • Recurly alternative
  • Paddle alternative
  • Chargebee alternative
  • Lago alternative

Company

  • About
  • Open Source
  • Terms
  • Privacy

Countries

  • Mexico
  • Argentina
  • Colombia
  • Chile
  • Peru
  • Ecuador
  • Uruguay
  • Paraguay
  • Bolivia
  • Panama
  • El Salvador
  • Brazil
XLinkedInGitHub