Commet
  • Pricing
Log InTry out

Migrate from Stripe Billing to Commet

A practical guide to moving subscriptions, usage metering, and webhooks from Stripe Billing to Commet — with side-by-side code for every step.


Introduction

Your billing logic is spread across Stripe Products, Prices, Subscriptions, and metadata — held together by a webhook sync layer that drifts out of sync. This guide shows you how to move it to Commet, step by step, with side-by-side code for every operation.

Commet is a billing platform for SaaS and AI products. It acts as Merchant of Record, so tax, compliance, refunds, and payouts are included — you stop being the seller of record.

Jump ahead to any section:

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

History

Stripe was founded in 2010 by Patrick and John Collison to make online payments programmable. Stripe Billing arrived in 2018 as a subscription layer on top of that payments infrastructure, and it serves every business model — from newspapers to gyms to SaaS.

That breadth is Stripe's strength. It's also why building usage-based SaaS billing on Stripe means assembling Products, Prices, Meters, Entitlements, and webhooks into a system you maintain yourself.

Commet was built for one job: measure consumption and charge for it. It's a plan-first billing platform for SaaS and AI products — plans define pricing and features, subscriptions bill automatically, and usage events drive metered charges without a reconciliation layer on your side.

The two are not direct substitutes. Stripe Billing is a toolkit where you remain the merchant; Commet is a complete billing system where Commet is the merchant. This guide covers what that difference means at each layer of your integration.

Concepts

Stripe gives you payment primitives; Commet gives you billing primitives. Here's how they map:

Stripe BillingCommet
Product + PricePlan (pricing, features, and intervals in one object)
SubscriptionSubscription
CustomerCustomer
Billing Meter + Meter EventsMetered feature + usage events
Entitlements / Features APIFeatures attached to plans, queried with featureAccess.get
Coupons + Promotion CodesPromo codes
Checkout SessioncheckoutUrl returned by subscriptions.create
Customer PortalCustomer Portal
Tax settings + Stripe TaxIncluded — Commet is Merchant of Record

The biggest structural difference: in Stripe, plan logic lives partly in Prices, partly in metadata, and partly in your database. In Commet, the plan is the single source of truth — your app reads entitlements from it instead of reconstructing them from webhook events.

Subscription statuses

Subscription lifecycles translate cleanly. These are Commet's statuses and what they mean for access:

Commet statusGrants access?Closest Stripe status
pending_paymentNoincomplete
trialingYestrialing
activeYesactive
past_dueYes (grace period)past_due
pausedNopaused
canceledNocanceled
expiredNoincomplete_expired

The rule of thumb is the same one you use today: 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 Stripe's Billing Meters.
  • credits: customers buy credit packs upfront; usage draws them down. No Stripe equivalent — this is the system teams build on top.
  • balance: a prepaid monetary balance debited per event, including per-token AI pricing. No Stripe equivalent.

Official SDKs

Both platforms ship official server-side SDKs. Commet adds framework and AI integrations on top:

SDK / toolStripeCommet
Node.js / TypeScriptstripe@commet/node
Pythonstripecommet
Gostripe-gocommet-go
Javastripe-javacommet-java
PHPstripe-phpcommet-php
Next.js helpers—@commet/next (webhook handler, route helpers)
AI SDK integration—commet-ai-sdk (token usage tracking)
Auth integration—commet-better-auth
CLIstripe CLIcommet 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 Stripe, subscribing a customer means creating a Customer, attaching a payment method or Checkout Session, and referencing a Price ID you look up or hardcode.

Stripe
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

const customer = await stripe.customers.create({
  email: 'billing@acme.com',
})

const session = await stripe.checkout.sessions.create({
  customer: customer.id,
  mode: 'subscription',
  line_items: [{ price: 'price_1AbCdEfGh', quantity: 1 }],
  success_url: 'https://acme.com/success',
  cancel_url: 'https://acme.com/cancel',
})

// redirect to session.url

In Commet, the same flow is two calls. Plans are referenced by a readable code you define, and subscriptions.create returns a checkout URL directly:

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. You can pass your own user ID everywhere instead of storing a cus_xxx mapping table.

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 whether a user has access replaces your webhook-fed state table:

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

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

Plan changes and proration

In Stripe, proration behavior is a set of flags (proration_behavior, billing_cycle_anchor) you configure per call. Commet ships one default policy: 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 — the same self-service surface Stripe's Customer Portal gives you, created automatically per customer.

Track usage / metering

Stripe's usage-based billing runs on Billing Meters. You create a Meter in the dashboard, attach it to a Price, and report meter events with the customer's Stripe ID:

Stripe
await stripe.billing.meterEvents.create({
  event_name: 'api_calls',
  payload: {
    stripe_customer_id: 'cus_NffrFeUfNV2Hib',
    value: '1',
  },
})

Stripe aggregates the events and invoices at period end. What it doesn't answer is whether the user can act right now — real-time gating (quotas, credits, prepaid balances) is yours to build.

In Commet, metered features are part of the plan, and tracking usage looks like this:

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. It replaces the deduplication you handle with identifier on Stripe meter events.

Real-time entitlement checks come from the same plan definition:

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 Stripe, this pipeline — model price lookup, margin, balance debit, overage invoicing — is code you write and keep current as model prices change.

Beyond metered billing, Commet plans support credits and prepaid balance as first-class consumption models. In Stripe, those are systems you build on top of meters. See usage-based billing for how the three models compare.

Webhooks

In Stripe, webhooks are load-bearing: subscription state, payment failures, and plan changes all reach your app as events you must consume, verify, and replay into your database.

Stripe
const event = stripe.webhooks.constructEvent(
  rawBody,
  signature,
  process.env.STRIPE_WEBHOOK_SECRET!,
)

switch (event.type) {
  case 'customer.subscription.updated':
    // Update your local subscription state
    break
  case 'invoice.payment_failed':
    // Flag the account
    break
}

In Commet, webhooks are notifications, not the source of truth — you can always query subscription state directly. When you do want them, @commet/next ships a typed handler that verifies signatures and routes events:

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, verify the HMAC-SHA256 signature with commet.webhooks.verifyAndParse({ rawBody, signature, secret }) — the signature arrives in the X-Commet-Signature header.

The events you handle today map directly:

Stripe eventCommet event
customer.subscription.createdsubscription.created
customer.subscription.updatedsubscription.updated / subscription.plan_changed
customer.subscription.deletedsubscription.canceled
customer.subscription.trial_will_endtrial.will_end
invoice.createdinvoice.created
invoice.payment_succeededpayment.received
invoice.payment_failedpayment.failed
charge.refundedpayment.refunded
charge.dispute.createdpayment.disputed

For local development, commet listen localhost:3000/api/webhooks/commet forwards live events to your machine — the same workflow as stripe listen, no tunneling tools needed.

Delivery mechanics, side by side:

StripeCommet
SignatureStripe-Signature header, HMAC-SHA256X-Commet-Signature header, HMAC-SHA256
RetriesExponential backoff, up to 3 daysExponential backoff, 8 attempts (1 min → 6 h)
Failed endpoint handlingDisabled after sustained failuresAuto-disabled after 3 consecutive failures, with email alert
Local developmentstripe listen --forward-tocommet listen localhost:3000/api/webhooks/commet

Taxes & compliance

This is the largest operational difference between the two platforms.

With Stripe Billing, you are the merchant of record. Stripe Tax calculates tax at checkout (as an add-on fee), but registering in each jurisdiction, filing returns, and staying compliant as thresholds change remain your responsibility.

With Commet, Commet is the Merchant of Record — it sells to your customer, so tax calculation, collection, and remittance are Commet's problem, along with refunds, disputes, and payouts. There is no tax configuration step in the migration; you delete yours.

Concretely, moving to a Merchant of Record removes four recurring tasks:

  • Tracking tax registration thresholds per jurisdiction as your revenue grows
  • Filing returns and remitting collected tax
  • Processing refunds and representing disputes as the seller
  • Reconciling payout reports against invoices for accounting

Commet also handles regional pricing: you define canonical USD prices, and can optionally price plans in local currencies — ARS, BRL, CLP, COP, PEN, UYU, PYG, BOB, MXN, CAD, and EUR — with the currency auto-detected from the customer's billing country at checkout.

One honest limitation to plan around: Commet checkout is card-only today. If a meaningful share of your revenue arrives through bank debits or wallets on Stripe, keep that flow on Stripe until your card-paying segment is migrated.

Pricing

Stripe BillingCommet
Billing fee0.5%–0.8% of invoiced volumeIncluded
Payment processing2.9% + $0.30 per transaction (US cards)Included
Tax calculationStripe Tax, additional feeIncluded
Tax filing & remittanceYours (or paid filing tools)Included — Merchant of Record
Total per successful transaction~3.4%–3.7% + $0.30, plus tax tooling4.5% + $0.40, all-in
International cards+1.5%+1.5% (non-US cards)
Disputes$15 per dispute$15 per dispute
RefundsProcessing fees not returnedFree

The comparison to make is not fee-vs-fee — it's all-in cost vs assembled cost. Commet's 4.5% + $0.40 covers processing, billing logic, tax, and Merchant of Record liability; the Stripe stack reaches a similar number once you add Stripe Tax and account for the engineering time that maintains the billing layer.

Full details on the pricing page.

Conclusion

One thing does not migrate automatically, so plan for it: historical invoices. Keep Stripe access for past invoices; 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.

The migration itself follows a predictable shape:

  1. Model your Products and Prices as Commet plans — the metadata sprawl becomes explicit features.
  2. Swap stripe.billing.meterEvents.create for commet.usage.track.
  3. Replace webhook-fed state tables with subscriptions.getActive and featureAccess.get.
  4. Delete your tax configuration — Commet is the Merchant of Record.
  5. Cut over at renewal, running one cycle in parallel to compare invoices.

Your customers keep paying by card; what changes is who assembles — and who is liable for — the billing system.

Start with the quickstart, or read the full Stripe Billing vs Commet comparison first.

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