Skip to main content
Business & GrowthPayment Services140 lines

Creem

Accept payments with Creem as merchant of record. Use this skill when the project

Quick Summary30 lines
You are a payments specialist who integrates Creem into projects. Creem is a merchant
of record platform for SaaS and digital products that handles payments, global tax
compliance, invoicing, and subscription management.

## Key Points

- Pass `referenceId` in metadata to link Creem records to your users
- Store Creem customer IDs and subscription IDs in your database
- Handle `subscription.past_due` with a grace period before restricting access
- Use test mode keys (`creem_test_*`) for development and testing
- Process all webhook events — don't just handle the happy path
- Send email notifications on subscription state changes
- Granting access on checkout redirect without webhook verification
- Not storing Creem customer/subscription IDs
- Ignoring `past_due` events — users lose access without warning
- Using production keys in development
- Not handling cancellation gracefully — honor the remaining paid period

## Quick Example

```bash
npm install creem
```

```typescript
const subscription = await creem.subscriptions.get(subscriptionId);
console.log(subscription.status); // active, past_due, canceled, etc.
```
skilldb get payment-services-skills/CreemFull skill: 140 lines
Paste into your CLAUDE.md or agent config

Creem Payment Integration

You are a payments specialist who integrates Creem into projects. Creem is a merchant of record platform for SaaS and digital products that handles payments, global tax compliance, invoicing, and subscription management.

Core Philosophy

Merchant of record for SaaS

Creem handles the legal and financial complexity of selling globally — tax collection, compliance, invoicing, and payouts. You focus on building your product while Creem is the legal seller.

Products configured in dashboard

Products and pricing are set up in the Creem dashboard. Your code references product IDs and creates checkout sessions. This keeps pricing logic out of your codebase.

Webhook-driven state management

All subscription state changes — activation, payment, cancellation, expiry — arrive via webhooks. Your database stays in sync by processing these events.

Setup

Install

npm install creem

Initialize

import { Creem } from 'creem';

const creem = new Creem({
  apiKey: process.env.CREEM_API_KEY,
  serverIdx: process.env.CREEM_API_KEY?.startsWith('creem_test_') ? 1 : 0,
});

Key Techniques

Create checkout session

const checkout = await creem.checkouts.create({
  productId: process.env.CREEM_PRO_PRODUCT_ID,
  successUrl: 'https://yourdomain.com/success',
  metadata: {
    referenceId: userId,
    plan: 'pro',
  },
});

// Redirect user to checkout.checkoutUrl

Subscription lookup

const subscription = await creem.subscriptions.get(subscriptionId);
console.log(subscription.status); // active, past_due, canceled, etc.

Customer management

const customer = await creem.customers.get(customerId);
console.log(customer.email, customer.subscriptions);

Webhook Processing

EventAction
checkout.completedGrant access
subscription.activeConfirm subscription active
subscription.paidRenew access, send receipt
subscription.trialingGrant trial access
subscription.past_dueWarn user, retry in progress
subscription.pausedRestrict access temporarily
subscription.canceledRevoke access
subscription.expiredRevoke access
export async function POST(req: Request) {
  const body = await req.text();
  const event = JSON.parse(body);

  const eventType = event.eventType;
  const metadata = event.object?.metadata || {};
  const userId = metadata.referenceId;
  const customerEmail = event.object?.customer?.email;

  const GRANT_EVENTS = [
    'checkout.completed', 'subscription.active',
    'subscription.paid', 'subscription.trialing',
  ];
  const REVOKE_EVENTS = [
    'subscription.expired', 'subscription.paused',
    'subscription.canceled',
  ];

  if (GRANT_EVENTS.includes(eventType)) {
    await grantAccess(userId, metadata.plan || 'pro');
    if (eventType === 'subscription.trialing') {
      await sendTrialStartedEmail(customerEmail);
    }
  } else if (REVOKE_EVENTS.includes(eventType)) {
    await revokeAccess(userId);
    await sendCancellationEmail(customerEmail);
  } else if (eventType === 'subscription.past_due') {
    await sendPaymentFailedEmail(customerEmail);
  }

  return new Response(JSON.stringify({ received: true }));
}

Best Practices

  • Pass referenceId in metadata to link Creem records to your users
  • Store Creem customer IDs and subscription IDs in your database
  • Handle subscription.past_due with a grace period before restricting access
  • Use test mode keys (creem_test_*) for development and testing
  • Process all webhook events — don't just handle the happy path
  • Send email notifications on subscription state changes

Anti-Patterns

  • Granting access on checkout redirect without webhook verification
  • Not storing Creem customer/subscription IDs
  • Ignoring past_due events — users lose access without warning
  • Using production keys in development
  • Not handling cancellation gracefully — honor the remaining paid period

Install this skill directly: skilldb add payment-services-skills

Get CLI access →