Creem

Creem checkout and webhook provider for the TanStack Start template.

Creem is the default provider. It implements the shared PaymentProvider interface in src/lib/payment/providers/creem.ts: it creates hosted checkouts, verifies webhook signatures, and handles a couple of lifecycle events.

Configuration

VariableWherePurpose
VITE_PAYMENT_PROVIDERBuild-timeSet to creem (the default) to select this provider.
CREEM_API_KEYWorker secretServer key for creating checkouts.
CREEM_SERVER_IDXWorker secretSelects the Creem server (sandbox vs. live). Defaults to 0.
CREEM_WEBHOOK_SECRETWorker secretHMAC secret for verifying webhook signatures.
VITE_CREEM_PRODUCT_PRO_MONTHLY / _PRO_YEARLY / _LIFETIMEBuild-timePublic Creem product IDs for the three plans.

Set the server values with wrangler secret put CREEM_API_KEY (and the others). See Environment Variables.

Prices vs. product IDs

src/config/price-config.ts holds the display prices. Each plan's productId comes from a VITE_CREEM_PRODUCT_* variable and is what Creem actually charges for. The display amount and the Creem product are independent — keep them consistent yourself.

Checkout

createCheckout builds a Creem checkout for the requested productId and returns its URL. The create route redirects the browser there, with success URL {origin}/pay-success?orderId={orderNo}.

Webhook

  • Path: POST /api/payment/notify/creem (served by the shared /api/payment/notify/$provider route).
  • Signature: HMAC-SHA256 of the raw body using CREEM_WEBHOOK_SECRET, compared against the hex creem-signature header with a constant-time check. A missing/invalid signature makes the route answer 400.
  • Handled events:
    • checkout.completed — marks the matching payment row success and stores the Creem order/customer IDs.
    • Subscription lifecyclesubscription.active, subscription.paid, subscription.update, subscription.trialing, subscription.paused, subscription.past_due, subscription.scheduled_cancel, subscription.canceled, and subscription.expired upsert the user's subscription row to the matching status, period, and cancel timestamps.
    • refund.created — marks the matching payment row refunded.

Local testing

  • Use the Creem sandbox server (CREEM_SERVER_IDX) and sandbox product IDs.
  • Expose your local webhook with a tunnel (e.g. cloudflared / ngrok) and register https://<tunnel>/api/payment/notify/creem in Creem.
  • Confirm the payment row flips to success after checkout.completed.

Production notes

  • Switch to the live Creem server and live product IDs (rebuild for the VITE_* IDs to take effect).
  • Webhooks can be retried; the handlers are not idempotent, so add event-id dedup if you extend them with non-terminal side effects.

Verify

  • Pricing CTA creates a checkout and redirects to Creem.
  • After paying in sandbox, the success page shows the order.
  • The webhook marks the payment row success.
  • A subscription product creates/updates the subscription row.

On this page