Skip to main content
@creem_io/nextjs is a small App Router adapter around the Creem SDK. It provides route-handler factories for checkout, the customer portal, and webhooks, plus React link components for starting checkout or opening the portal.

Source and example

Browse the package and its runnable Next.js application.

Install

The adapter requires Next.js 13 or newer, React 18 or newer, and the App Router.

Create a checkout route

Keep the API key on the server:
.env.local
Create the route used by the checkout component:
app/checkout/route.ts
Render a link to that route:
app/pricing/page.tsx
CreemCheckout builds an anchor whose query parameters are consumed by Checkout. It supports:
Component props become client-controlled query parameters. For authenticated billing, wrap the checkout handler and derive customer, referenceId, requestId, allowed product IDs, and the success URL from trusted server-side state. Do not use query parameters as proof of identity or entitlement.

Protect an authenticated checkout

The route below accepts the selected product from the link, checks it against a server-side allowlist, and replaces identity-related parameters with values from the authenticated session. successUrl is removed from the request so the handler always uses the server-configured default.
app/checkout/route.ts
requireUser represents your application’s server-side authentication check. It should reject an unauthenticated request and return the user from the verified session. getOrCreateCheckoutAttemptId represents a server-side idempotency record: retries of one purchase attempt should reuse its ID, while a later intentional purchase gets a new one. Validate or overwrite other commercial inputs, such as units and discountCode, when customers should not be able to choose them freely.

Create a customer portal route

app/portal/route.ts
CreemPortal uses /portal by default. Set portalPath when your route lives elsewhere.
The portal handler accepts a customer ID from the URL and does not authenticate it. Before using this pattern in an application, require a session and verify that the requested customer belongs to the signed-in account. Never expose an unprotected portal route.

Handle webhooks

Create a POST route and register its public URL in the Creem dashboard:
app/api/webhook/creem/route.ts
Webhook reads the raw request body, verifies the creem-signature header, parses the event, and calls the matching handler. Keep every callback idempotent because delivery may be retried.

Access callbacks

onGrantAccess runs for active, trialing, and paid subscription events. onRevokeAccess runs for paused, expired, and canceled subscription events. subscription.scheduled_cancel only calls onSubscriptionScheduledCancel and does not change access.

Event callbacks

The adapter also exposes callbacks for individual events:
  • onCheckoutCompleted
  • onRefundCreated
  • onDisputeCreated
  • onSubscriptionActive
  • onSubscriptionTrialing
  • onSubscriptionCanceled
  • onSubscriptionPaid
  • onSubscriptionExpired
  • onSubscriptionUnpaid
  • onSubscriptionUpdate
  • onSubscriptionPastDue
  • onSubscriptionPaused
  • onSubscriptionScheduledCancel
Each callback receives normalized event data, including webhookId, webhookCreatedAt, and the relevant customer, product, order, or subscription fields.

Common gotchas

Event names and access reasons

The event is subscription.canceled (one L), and its callback is onSubscriptionCanceled. A misspelled event type sent to Webhook returns 400 Unknown event type; a misspelled string comparison in your callback never matches. Access callback reasons use underscores, such as subscription_expired, rather than dotted event names like subscription.expired. subscription.canceled calls onRevokeAccess with reason subscription_canceled, then calls onSubscriptionCanceled. The adapter awaits each callback; if revocation throws, it returns an error for retry and does not call onSubscriptionCanceled on that attempt.

SDK and webhook field casing

The creem SDK exposes camelCase subscription fields such as currentPeriodEndDate and canceledAt. Next.js webhook callbacks retain snake_case fields such as current_period_end_date and canceled_at. Account for this difference when sharing utilities between SDK responses and webhook callbacks.

Timestamp formats

The event envelope’s created_at becomes webhookCreatedAt in event callbacks and is a millisecond timestamp. Transaction created_at fields also use milliseconds. Subscription date fields such as current_period_end_date arrive as ISO 8601 strings in the webhook JSON; the adapter does not convert them to Date objects. The SDK converts its subscription date fields to Date objects when parsing API responses.

Checkout amounts

In onCheckoutCompleted, the checkout amount and currency are on order, not at the top level. The order field is optional, so check it before reading the amount:

Metadata in access callbacks

onGrantAccess and onRevokeAccess receive subscription fields directly, including metadata. Read metadata?.referenceId rather than context.object.metadata. Derive that reference ID from trusted server-side state when creating an authenticated checkout, and check that it is a string before using it, as shown in the webhook example.

Route factory reference

Checkout(options)

Portal(options)

Webhook(options)

webhookSecret is required. All event and access callbacks are optional.

Better Auth

Use authenticated billing endpoints with persisted subscription state.

Next.js template

Start from a full application with authentication and a database.