| name | CREEM API Integration |
| description | Integrate CREEM payment infrastructure for checkouts, subscriptions, free products, licenses, and webhooks. Supports one-time payments, recurring billing, free products, and MoR compliance. |
| noindex | true |
CREEM API Integration Skill
You are an expert at integrating CREEM, a Merchant of Record (MoR) payment platform for SaaS and digital businesses. You help developers implement checkout flows, manage subscriptions, handle webhooks, and work with license keys.
Core Concepts
CREEM acts as the legal seller (Merchant of Record), handling tax compliance, payment processing, and refunds. When integrating:
- Production API:
https://api.creem.io
- Test API:
https://test-api.creem.io
- Authentication:
x-api-key header with API key from dashboard
- Prices: Always in cents (1000 = $10.00). Use
0 for free products.
- Currencies: Three-letter ISO codes in uppercase (USD, EUR, etc.)
Authentication Setup
Always configure authentication properly:
const API_KEY = process.env.CREEM_API_KEY;
const BASE_URL = process.env.NODE_ENV === 'production'
? 'https://api.creem.io'
: 'https://test-api.creem.io';
const headers = {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
};
Quick Reference: API Endpoints
Checkouts
| Method | Endpoint | Description |
|---|
| POST | /v1/checkouts | Create checkout session |
| GET | /v1/checkouts?checkout_id={id} | Retrieve checkout |
Products
| Method | Endpoint | Description |
|---|
| POST | /v1/products | Create product |
| GET | /v1/products?product_id={id} | Retrieve product |
| GET | /v1/products/search | List all products |
Customers
| Method | Endpoint | Description |
|---|
| GET | /v1/customers?customer_id={id} | Retrieve customer |
| GET | /v1/customers?email={email} | Retrieve by email |
| GET | /v1/customers/list | List all customers |
| POST | /v1/customers/billing | Generate portal link |
Subscriptions
| Method | Endpoint | Description |
|---|
| GET | /v1/subscriptions?subscription_id={id} | Retrieve subscription |
| POST | /v1/subscriptions/{id} | Update subscription |
| POST | /v1/subscriptions/{id}/upgrade | Upgrade plan |
| POST | /v1/subscriptions/{id}/cancel | Cancel subscription |
| POST | /v1/subscriptions/{id}/pause | Pause subscription |
| POST | /v1/subscriptions/{id}/resume | Resume subscription |
Licenses
| Method | Endpoint | Description |
|---|
| POST | /v1/licenses/activate | Activate license |
| POST | /v1/licenses/validate | Validate license |
| POST | /v1/licenses/deactivate | Deactivate license |
Discounts
| Method | Endpoint | Description |
|---|
| POST | /v1/discounts | Create discount |
| GET | /v1/discounts?discount_id={id} | Retrieve discount |
| GET | /v1/discounts?discount_code={code} | Retrieve by code |
| DELETE | /v1/discounts/{id}/delete | Delete discount |
Transactions
| Method | Endpoint | Description |
|---|
| GET | /v1/transactions?transaction_id={id} | Get transaction |
| GET | /v1/transactions/search | List transactions |
Implementation Patterns
1. Create a Checkout Session
The most common integration pattern - redirect users to CREEM's hosted checkout:
const createCheckout = async (productId: string, options?: {
requestId?: string;
successUrl?: string;
customerEmail?: string;
discountCode?: string;
units?: number;
metadata?: Record<string, any>;
}) => {
const response = await fetch(`${BASE_URL}/v1/checkouts`, {
method: 'POST',
headers,
body: JSON.stringify({
product_id: productId,
request_id: options?.requestId,
success_url: options?.successUrl,
customer: options?.customerEmail ? { email: options.customerEmail } : undefined,
discount_code: options?.discountCode,
units: options?.units,
metadata: options?.metadata
})
});
const checkout = await response.json();
return checkout;
};
Success URL query parameters after payment:
checkout_id - Checkout session ID
order_id - Order created
customer_id - Customer ID
subscription_id - Subscription (if recurring)
product_id - Product purchased
request_id - Your tracking ID (if provided)
signature - HMAC signature for verification
2. Webhook Handler
CRITICAL: Always verify webhook signatures to prevent fraud.
import crypto from 'crypto';
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return computed === signature;
}
export async function handleWebhook(req: Request) {
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 });
}
const event = JSON.parse(rawBody);
switch (event.eventType) {
case 'checkout.completed':
await handleCheckoutCompleted(event.object);
break;
case 'subscription.paid':
await handleSubscriptionPaid(event.object);
break;
case 'subscription.canceled':
await handleSubscriptionCanceled(event.object);
break;
case 'subscription.expired':
await handleSubscriptionExpired(event.object);
break;
case 'refund.created':
await handleRefund(event.object);
break;
}
return new Response('OK', { status: 200 });
}
3. License Key Management
For desktop apps, CLI tools, or software requiring activation:
const activateLicense = async (licenseKey: string, instanceName: string) => {
const response = await fetch(`${BASE_URL}/v1/licenses/activate`, {
method: 'POST',
headers,
body: JSON.stringify({
key: licenseKey,
instance_name: instanceName
})
});
const result = await response.json();
return result;
};
const validateLicense = async (licenseKey: string, instanceId: string) => {
const response = await fetch(`${BASE_URL}/v1/licenses/validate`, {
method: 'POST',
headers,
body: JSON.stringify({
key: licenseKey,
instance_id: instanceId
})
});
const result = await response.json();
return result;
};
const deactivateLicense = async (licenseKey: string, instanceId: string) => {
const response = await fetch(`${BASE_URL}/v1/licenses/deactivate`, {
method: 'POST',
headers,
body: JSON.stringify({
key: licenseKey,
instance_id: instanceId
})
});
return response.json();
};
4. Subscription Management
const updateSubscriptionSeats = async (subscriptionId: string, itemId: string, newUnits: number) => {
const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}`, {
method: 'POST',
headers,
body: JSON.stringify({
items: [{ id: itemId, units: newUnits }],
update_behavior: 'proration-charge-immediately'
})
});
return response.json();
};
const upgradeSubscription = async (subscriptionId: string, newProductId: string) => {
const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}/upgrade`, {
method: 'POST',
headers,
body: JSON.stringify({
product_id: newProductId,
update_behavior: 'proration-charge-immediately'
})
});
return response.json();
};
const cancelSubscription = async (subscriptionId: string, immediate: boolean = false) => {
const response = await fetch(`${BASE_URL}/v1/subscriptions/${subscriptionId}/cancel`, {
method: 'POST',
headers,
body: JSON.stringify({
mode: immediate ? 'immediate' : 'scheduled'
})
});
return response.json();
};
5. Customer Portal
Let customers manage their own subscriptions:
const getCustomerPortalLink = async (customerId: string) => {
const response = await fetch(`${BASE_URL}/v1/customers/billing`, {
method: 'POST',
headers,
body: JSON.stringify({ customer_id: customerId })
});
const { customer_portal_link } = await response.json();
return customer_portal_link;
};
Webhook Events Reference
| Event | When | Action |
|---|
checkout.completed | Payment successful | Grant access, create user |
subscription.active | New subscription created | Sync to database |
subscription.paid | Recurring payment processed | Extend access period |
subscription.canceled | User/merchant canceled | Revoke at period end |
subscription.expired | Period ended without payment | Retries may happen |
subscription.trialing | Trial started | Grant trial access |
subscription.paused | Subscription paused | Pause features |
subscription.update | Subscription modified | Sync changes |
refund.created | Refund processed | May revoke access |
dispute.created | Chargeback opened | Handle dispute |
Error Handling
All endpoints return standard HTTP status codes:
const handleApiResponse = async (response: Response) => {
if (response.ok) {
return response.json();
}
switch (response.status) {
case 400: throw new Error('Bad Request - Check parameters');
case 401: throw new Error('Unauthorized - Invalid API key');
case 403: throw new Error('Forbidden - Insufficient permissions or limit reached');
case 404: throw new Error('Not Found - Resource does not exist');
case 429: throw new Error('Rate Limited - Too many requests');
case 500: throw new Error('Server Error - Contact support');
default: throw new Error(`Unexpected error: ${response.status}`);
}
};
Test Mode
Always develop in test mode first:
- Use
https://test-api.creem.io as base URL
- Use test API key from dashboard
- Test cards:
4111 1111 1111 1111 - Success
4507 9900 0000 0028 - Declined
4507 9900 0000 0010 - Insufficient funds
Common Integration Checklist
When implementing CREEM:
-
Environment Setup
-
Checkout Flow
-
Subscription Handling
-
License Keys (if applicable)
-
Security
File References
For detailed information, see:
REFERENCE.md - Complete API reference with all fields
WEBHOOKS.md - Webhook payload examples
WORKFLOWS.md - Step-by-step integration patterns
Need Help?