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
Cancellation does not necessarily revoke access immediately. A subscription scheduled to cancel can remain usable until its paid period expires.
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.