Creem
How to set up and use Creem for payments and subscriptions
MkSaaS supports Creem for payment processing, supporting both one-time payments and subscriptions.
Setup
MkSaaS template provides three pricing plans by default: a free plan, a pro subscription plan (monthly/yearly), and a lifetime plan (one-time payment). Follow these steps to set up:
Create Creem Account
Create a Creem account at creem.io. No credit card required to sign up.
Get API Keys
Get your API keys from the Creem Dashboard:
- Go to Creem Dashboard >
Developers>API & Webhooks, create a new API Key - Copy the API key (Note: test mode starts with
creem_test_, production mode starts withcreem_live_) - Save it to your environment file as
CREEM_API_KEY
Set Up Webhook
Set up Webhook and get your Webhook secret:
- Go to Creem Dashboard >
Developers>API & Webhooks, create a new Webhook - Add Webhook URL:
https://YOUR-DOMAIN.com/api/webhooks/creem - Creem automatically listens for all payment and subscription related events, including:
checkout.completedsubscription.activesubscription.paidsubscription.updatesubscription.canceledsubscription.scheduled_cancelsubscription.expiredsubscription.past_duesubscription.unpaidsubscription.pausedsubscription.trialing
- Copy the Webhook signing secret
- Save it to your environment file as
CREEM_WEBHOOK_SECRET
Create Products and Pricing Plans
Create products in Creem and set up pricing plans:
- Go to Creem Dashboard >
Commerce>Products - Create the Pro subscription plan product:
- Click to create a product
- Name:
Pro Plan - Description:
Premium features with subscription pricing - Add monthly subscription price:
- Price: $9.90 (currency: USD)
- Billing type:
recurring - Billing period:
every-month - Save and copy the Product ID (starts with
prod_), this will be used forNEXT_PUBLIC_CREEM_PRODUCT_PRO_MONTHLY
- Copy the monthly subscription product, then add yearly subscription price:
- Price: $99.00 (currency: USD)
- Billing type:
recurring - Billing period:
every-year - Save and copy the Product ID (starts with
prod_), this will be used forNEXT_PUBLIC_CREEM_PRODUCT_PRO_YEARLY
- Create the Lifetime plan product:
- Click to create a product
- Name:
Lifetime Plan - Description:
One-time payment for lifetime access - Add price:
- Price: $199.00 (currency: USD)
- Billing type:
one_time - Save and copy the Product ID (starts with
prod_), this will be used forNEXT_PUBLIC_CREEM_PRODUCT_LIFETIME
Configure Environment Variables
Add the following environment variables:
# Payment provider
NEXT_PUBLIC_PAYMENT_PROVIDER=creem
# Set to 'true' to use Creem test API, omit or set to 'false' for production
# CREEM_DEBUG=true
# API Key and Webhook Secret
CREEM_API_KEY=creem_test_...
CREEM_WEBHOOK_SECRET=...
# Product IDs
NEXT_PUBLIC_CREEM_PRODUCT_PRO_MONTHLY=prod_...
NEXT_PUBLIC_CREEM_PRODUCT_PRO_YEARLY=prod_...
NEXT_PUBLIC_CREEM_PRODUCT_LIFETIME=prod_...Update Website Configuration
Update the payment and price sections in src/config/website.tsx. The
active provider and product IDs resolve from NEXT_PUBLIC_PAYMENT_PROVIDER
and NEXT_PUBLIC_CREEM_PRODUCT_*.
You must configure this section to match the products you created in Creem:
payment: {
provider: paymentProvider,
price: {
plans: {
free: {
id: 'free',
prices: [],
isFree: true,
isLifetime: false,
},
pro: {
id: 'pro',
prices: [
{
type: 'subscription',
priceId: priceIds.proMonthly,
amount: 990, // amount in cents ($9.90)
currency: 'USD',
interval: 'month',
},
{
type: 'subscription',
priceId: priceIds.proYearly,
amount: 9900, // amount in cents ($99.00)
currency: 'USD',
interval: 'year',
},
],
isFree: false,
isLifetime: false,
popular: true,
},
lifetime: {
id: 'lifetime',
prices: [
{
type: 'one_time',
priceId: priceIds.lifetime,
amount: 19900, // amount in cents ($199.00)
currency: 'USD',
allowPromotionCode: true,
},
],
isFree: false,
isLifetime: true,
},
},
},
},If you are setting up your environment, you can now go back to the Environment Configuration and continue. The rest of this document can be read later.
Environment Configuration
Set up environment variables
Development Environment
Keep the application on its normal local origin and use an HTTPS tunnel only
for webhook delivery. Start MkSaaS, then expose port 3000 with Cloudflared in
another terminal:
pnpm dev
# Run in a second terminal
cloudflared --config /dev/null tunnel \
--url http://localhost:3000 \
--no-autoupdate \
--protocol quicIn the Creem Dashboard, switch to Test Mode and register the generated URL as
https://xxxx.trycloudflare.com/api/webhooks/creem. Keep
NEXT_PUBLIC_BASE_URL=http://localhost:3000; the tunnel is only for Creem's
webhook request. Creem returns the browser directly to the Billing page, while
the signed webhook updates access in the background.
The template also includes a Creem Test Mode E2E flow:
pnpm e2e:creemDemo video: https://cdn.mksaas.com/mksaas/video/mksaas-creem-payment.mp4
Creem provides a full test environment. When using API keys starting with creem_test_, all operations run in a sandbox and no real transactions are made.
For automated sandbox setup, server ports, environment-file precedence, and database prerequisites, see Automated Testing and tests/e2e/creem/README.md in the template.
Production Environment
- Go to Creem Dashboard >
Developers>API & Webhooks - Add the production Webhook URL:
https://YOUR-DOMAIN.com/api/webhooks/creem - Switch the API key from test key (
creem_test_) to production key (creem_live_) - Make sure your environment variables are updated with production keys and product IDs
Webhook Events
Creem supports the following webhook events:
| Event | Description |
|---|---|
checkout.completed | Checkout session completed |
subscription.active | New subscription created (for sync only) |
subscription.paid | Subscription payment successful (use to activate access) |
subscription.update | Subscription updated (plan change, period renewal) |
subscription.canceled | Subscription canceled |
subscription.scheduled_cancel | Subscription marked to cancel at end of billing period |
subscription.past_due | Payment failed, subscription awaiting retry |
subscription.expired | Payment expired but can still recover; stored as past_due |
subscription.unpaid | Collection attempts are exhausted; access is suspended |
subscription.trialing | Subscription entered trial period |
subscription.paused | Subscription paused |
Creem recommends using the subscription.paid event to activate user access, rather than subscription.active. subscription.active is only for data synchronization.
Only subscription.canceled is terminal. An expired or past-due subscription
can become active again after a successful payment.
Webhook Responses and Retries
| Result | HTTP status |
|---|---|
| Creem is not the configured provider | 404 |
Missing payload or creem-signature header | 400 |
| Invalid signature | 400 |
| Successfully processed or safely duplicated | 200 |
| Temporary provider or database failure | 500 |
Temporary failures return 500 so Creem can retry. Handlers are idempotent,
so repeated deliveries do not create duplicate payments.
Creem refund and dispute events are not handled by the template. Refunding a
payment in Creem does not automatically revoke access; revoke it manually or
add a handler in src/payment/provider/creem.ts.
Webhook Signature Verification
Creem uses the HMAC-SHA256 algorithm to sign webhook requests. The signature is sent via the creem-signature request header.
MkSaaS uses Web Crypto API for signature verification (compatible with Cloudflare Workers):
async function verifySignature(payload: string, signature: string, secret: string): Promise<boolean> {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(payload));
const computed = Array.from(new Uint8Array(signatureBuffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return computed === signature;
}Customer Portal
After every successful payment, your customers will receive an email with a link to their Customer Portal. The Customer Portal allows them to:
- View subscription details
- Manage subscriptions (upgrade, pause, cancel)
- View payment history
- Update payment methods
Test Cards
To test Creem integration, use Creem's test mode with test credit cards:
- 4242 4242 4242 4242 - Successful payment
- 4000 0000 0000 0002 - Payment failure
When using test mode (API keys starting with creem_test_), all transactions run in a sandbox environment with no real charges.
Best Practices
- Protect API keys: Never expose your Creem API key in client-side code
- Validate webhook signatures: Always verify the
creem-signatureof webhook events - Handle errors gracefully: Provide user-friendly error messages when payments fail
- Test webhooks thoroughly: Make sure all webhook events are handled correctly
MkSaaS Docs