Jerbix

Creem

Creem product mapping, sandbox testing, and webhook setup

Last updated November 3, 2026


Creem is a Merchant of Record: it collects in its own name and handles tax and refunds, so the site receives net settlements. Both subscriptions and one-time payments are supported.

Steps

  1. Create products in the Creem dashboard: recurring products for subscriptions (one per

interval) and one-time products for lifetime purchases.

  1. Fill in the environment variables:

``dotenv VITE_PAYMENT_PROVIDER=creem CREEM_API_KEY=creem_xxx CREEM_WEBHOOK_SECRET=whsec_xxx CREEM_PRODUCTS=pro_monthly=prod_xxx,pro_yearly=prod_yyy,lifetime=prod_zzz ``

The left side of CREEM_PRODUCTS is the reference on the right-hand side of prices in src/config/products.ts; the right side is the Creem product ID (prod_*). Separate entries with commas, and map each reference to exactly one product ID.

  1. Deploy: pnpm run deploy.
  2. In the Creem dashboard add the webhook endpoint <your-site-domain>/api/webhooks/creem, use

CREEM_WEBHOOK_SECRET as the signing secret, and subscribe to the events below.

The endpoint only responds while Creem is the active provider: when the site runs a different channel it answers 404, and Creem stops retrying.

Events

EventPurpose
checkout.completedCheckout finished; one-time orders are recorded here
subscription.paidThe authoritative subscription payment event, sent for the first payment and every renewal
subscription.*Subscription lifecycle; scheduled_cancel normalizes to active with cancel-at-period-end

The creem-signature header is the HMAC-SHA256 hex digest of the raw request body; an invalid signature returns 400. Ownership travels in the checkout metadata.userId / productId / offerId, and subscription metadata wins when present. If both are missing, the write fails and Creem retries — a payment is never attributed to a guessed user.

Sandbox testing

With CREEM_DEBUG=true requests go to the sandbox at test-api.creem.io (production is api.creem.io). Test with a sandbox API key (starting with creem_test_) and sandbox product IDs; before launch set it back to false and switch to production keys and product IDs.

Differences from Stripe

DimensionCreemStripe
Products and pricesThe product is the price; there is no lookup key and checkout takes product_idPrices and products are separate objects, linked by lookup_key
Fetching pricesOne GET /v1/products/{id} per referencePrices are queried in batches by lookup key
Cancel redirectNo cancel_url; cancelling lands on Creem's own pageHas cancel_url, returning to the page that started the purchase
AmountsPre-tax; the checkout page adds tax, so the displayed price may differ from the charged totalDisplayed price equals the charged amount
Subscription statusexpired is recorded as past_due (still recoverable); unknown statuses become incomplete, withholding access for nowStatus is taken from Stripe as-is
RefundsNo in-app refund entry point; refunds are started in the Creem dashboardNo in-app refund entry point; refunds are started in the Stripe dashboard and charge.refunded comes back

Existing subscriptions do not migrate when you switch providers: switching means changing the merchant of record, so handle the existing subscribers yourself.

Verify

  • pnpm run check:env passes, so the provider, credentials, and product mapping are in place.
  • The Creem dashboard's webhook log shows deliveries from your site (200 means the signature

verified).