Creem is a Merchant of Record (MoR) payment platform. Creem is the legal seller, so it owns tax compliance, payment processing, chargebacks, and refunds.
Non-obvious rules
- Production API:
https://api.creem.io - Test API:
https://test-api.creem.io - Authentication:
x-api-keyheader. The key is a merchant credential: it must never reach a browser, mobile app, or desktop binary. Client apps call your backend, which calls Creem. - Prices: integers in cents (
1000= $10.00). Use0for free products. - Currencies: uppercase three-letter ISO codes (
USD,EUR). - Access is granted by webhook, not by the success redirect. The redirect can be forged or simply never happen if the customer closes the tab.
Reference files
Read the file that matches the task. Each is self-contained; do not read all of them up front.
| Need | Read |
|---|---|
| Exact endpoint, request body, or response field | REFERENCE.md |
| Webhook event payloads, signature verification, retry policy | WEBHOOKS.md |
| A complete integration walkthrough for a business model | WORKFLOWS.md |
REFERENCE.md covers Checkouts, Products, Customers, Subscriptions, Licenses,
Discounts, and Transactions. WORKFLOWS.md covers basic SaaS subscription,
one-time purchase with digital delivery, license keys for desktop apps,
seat-based team billing, freemium upgrade flows, and affiliate tracking.
Webhook signature verification
Every webhook handler must verify the signature before trusting the payload. An unverified handler lets anyone grant themselves paid access.
import crypto from "crypto";
function verifyWebhookSignature(payload: string, signature: string, secret: string) {
const computed = crypto.createHmac("sha256", secret).update(payload).digest("hex");
// timingSafeEqual throws on a length mismatch, so check that first.
if (signature.length !== computed.length) return false;
return crypto.timingSafeEqual(Buffer.from(computed, "hex"), Buffer.from(signature, "hex"));
}
// Verify against the RAW body, before JSON.parse.
const signature = req.headers.get("creem-signature");
const rawBody = await req.text();
if (!verifyWebhookSignature(rawBody, signature!, process.env.CREEM_WEBHOOK_SECRET!)) {
return new Response("Invalid signature", { status: 401 });
}
Events that drive access decisions. Creem emits 13 event types in total;
WEBHOOKS.md documents all of them with their payloads:
| Event | Action |
|---|---|
checkout.completed |
Grant access, create the user record |
subscription.paid |
Extend the access period |
subscription.canceled |
Revoke at period end |
subscription.expired |
Period ended without payment; retries may follow |
refund.created |
Consider revoking access |
dispute.created |
Chargeback opened; handle the dispute |
Test mode
Develop against https://test-api.creem.io with a test API key.
| Card | Behaviour |
|---|---|
4111 1111 1111 1111 |
Success |
4507 9900 0000 0028 |
Declined |
4507 9900 0000 0010 |
Insufficient funds |
Error handling
| Status | Meaning |
|---|---|
| 400 | Bad request; check parameters |
| 403 | Invalid or missing API key, or insufficient permissions (auth errors are 403) |
| 404 | Resource does not exist |
| 429 | Rate limited |
| 500 | Server error; contact support |
Integration checklist
When implementing Creem:
-
Environment setup
- Store API key in environment variables
- Configure base URL for test/production
- Set up webhook endpoint
-
Checkout flow
- Create checkout session with product_id
- Include request_id for tracking
- Set success_url with verification
- Handle checkout.completed webhook
-
Subscription handling
- Handle subscription.paid for renewals
- Handle subscription.canceled for access revocation
- Implement customer portal link
- Store subscription_id for management
-
License keys (if applicable)
- Implement activate on first use
- Validate on each app start
- Handle deactivation for device transfer
-
Security
- Verify webhook signatures
- Never expose API keys client-side
- Validate success URL signatures
Convex apps
If the project has a convex/ directory, do NOT hand-roll checkout routes and
webhook handlers with the raw API. Use the @creem_io/convex component: it owns
the webhook route, syncs billing state into the Convex database, and ships
connected React/Svelte widgets.
Route by task:
| Task | Fetch |
|---|---|
| First-time setup, or migrating from another billing provider | https://docs.creem.io/code/sdks/convex/integration.md |
| Add or change subscription plans, cycles, trials, unit pricing | https://docs.creem.io/code/sdks/convex/subscriptions.md |
| Sell one-time products, consumables, or credit packs | https://docs.creem.io/code/sdks/convex/one-time-and-credits.md |
| Gate a feature, read billing state, add account UI | https://docs.creem.io/code/sdks/convex/entitlements.md |
| Understand the billing entity, state model, or API contract | https://docs.creem.io/code/sdks/convex/concepts.md |
| Custom auth/RBAC, webhook middleware, checkout gates, i18n | https://docs.creem.io/code/sdks/convex/advanced.md |
| Upgrade from 0.3.x, or retire another billing provider | https://docs.creem.io/code/sdks/convex/migration.md |
| Look up an exact method signature or widget prop | https://docs.creem.io/code/sdks/convex/reference.md |
The integration guide is the sequential setup script — follow it in order and run its validation steps. The other pages are intent lookups for ongoing work.
Other SDKs
Prefer an official SDK over raw fetch when one fits the stack:
| Stack | Fetch |
|---|---|
| Any Node or browser runtime | https://docs.creem.io/code/sdks/typescript.md |
| Next.js | https://docs.creem.io/code/sdks/nextjs.md |
| Better Auth | https://docs.creem.io/code/sdks/better-auth.md |
Need help?
- Documentation: https://docs.creem.io
- Dashboard: https://creem.io/dashboard
- Support: support@creem.io