Skip to main content
When webhookSecret is configured, the plugin registers a verified webhook handler at:
/api/auth is Better Auth’s default base path. The plugin route itself is /creem/webhook. Add the full public URL in the Creem dashboard. Test-mode webhooks and production webhooks have separate configuration and signing secrets.

Persist subscription state

With persistSubscriptions: true, verified webhook events update the plugin’s local customer and subscription fields. Your application can query that state through hasAccessGranted() without making a Creem API request on each page load. Webhook delivery is asynchronous. Treat the success redirect as confirmation that checkout completed in the browser, not as the source of truth for granting durable access.

Grant and revoke access

Use the high-level callbacks for application-specific provisioning:
lib/auth.ts
onGrantAccess runs with one of these reasons:
  • subscription_active
  • subscription_trialing
  • subscription_paid
onRevokeAccess runs with:
  • subscription_paused
  • subscription_expired
  • subscription_canceled
subscription.scheduled_cancel preserves access until current_period_end_date. With persistence enabled, the plugin stores status: "scheduled_cancel" and cancelAtPeriodEnd: true; hasAccessGranted() returns false once that period ends, even if the final webhook is delayed. This event calls onSubscriptionScheduledCancel without calling onGrantAccess or onRevokeAccess. subscription.canceled means the subscription has ended. It calls both onRevokeAccess and onSubscriptionCanceled, and the access check denies that subscription. When cancellation is undone or finalized, subsequent subscription events synchronize the status and clear cancelAtPeriodEnd when the status is no longer scheduled_cancel. With persistence enabled, delayed scheduled-cancellation updates cannot change a stored canceled or expired subscription back to scheduled_cancel. Ignored subscription.scheduled_cancel events do not invoke onSubscriptionScheduledCancel. The general onSubscriptionUpdate callback still receives subscription.update events, including those whose scheduled status was ignored by persistence. If you manage access outside the plugin database, store the scheduled period end and enforce it in your own access checks.
Webhooks can be retried or delivered more than once. Make every callback idempotent: applying the same event twice must produce the same final state.

Event-specific callbacks

Use event-specific callbacks when you need more than the high-level access decision: Each callback receives normalized event data followed by the Better Auth endpoint context:

Trial tracking

Persistence adds hadTrial to the user model. When a user enters a trial, the plugin records it and can tell Creem to skip later trials for that user. This protects the Better Auth account boundary; it is not a substitute for any additional identity or abuse controls your product requires.

Custom webhook handlers

Most applications should use the plugin route. If you own a separate webhook route, verify the raw body before parsing it:
Do not configure both the plugin route and a custom route for the same webhook unless duplicate processing is intentional.