npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

creem-convex

v0.1.0

Published

Creem payments integration for Convex — checkouts, webhooks, subscriptions with real-time reactivity

Downloads

74

Readme

creem-convex

npm version License: MIT Tests TypeScript

Creem payments for Convex — drop-in integration with real-time reactivity. Checkouts, subscriptions, webhooks, and customer management in minutes.

Why creem-convex?

  • One-line schema...creemTables adds 4 indexed tables to your Convex schema
  • Real-time by default — subscriptions, payments, and access status update instantly via Convex reactivity
  • Webhook-first — HMAC-SHA256 verified handler with grant/revoke access pattern built in
  • Type-safe — Full TypeScript types for every Creem API object
  • Zero dependencies — Only peer deps on convex and optionally react

Quick Start

1. Install

npm install creem-convex

2. Add tables to your schema

// convex/schema.ts
import { defineSchema } from "convex/server";
import { creemTables } from "creem-convex/schema";

export default defineSchema({
  ...creemTables,
  // your other tables
});

This adds creem_payments, creem_subscriptions, creem_customers, and creem_webhook_events with proper indexes.

3. Set environment variables

In your Convex dashboard → Settings → Environment Variables:

CREEM_API_KEY=creem_your_api_key
CREEM_WEBHOOK_SECRET=whsec_your_webhook_secret
CREEM_TEST_MODE=true

4. Create your Creem actions

// convex/creem.ts
import { action, internalMutation, query } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import {
  creemCreateCheckout,
  syncCheckoutToDb,
  syncSubscriptionToDb,
  querySubscriptionByCustomer,
  queryHasActiveSubscription,
  queryPaymentsByCustomer,
} from "creem-convex";

// --- Actions (call Creem API) ---

export const createCheckout = action({
  args: {
    productId: v.string(),
    successUrl: v.string(),
    customerEmail: v.optional(v.string()),
  },
  handler: async (_ctx, args) => {
    return await creemCreateCheckout({
      productId: args.productId,
      successUrl: args.successUrl,
      customer: args.customerEmail ? { email: args.customerEmail } : undefined,
    });
  },
});

// --- Mutations (sync webhook data to DB) ---

export const syncPayment = internalMutation({
  args: { checkout: v.any() },
  handler: async (ctx, args) => {
    await syncCheckoutToDb(ctx.db, args.checkout);
  },
});

export const syncSubscription = internalMutation({
  args: { subscription: v.any() },
  handler: async (ctx, args) => {
    await syncSubscriptionToDb(ctx.db, args.subscription);
  },
});

// --- Queries (real-time!) ---

export const getSubscription = query({
  args: { creemCustomerId: v.string() },
  handler: async (ctx, args) => {
    return await querySubscriptionByCustomer(ctx.db, args.creemCustomerId);
  },
});

export const hasAccess = query({
  args: { creemCustomerId: v.string() },
  handler: async (ctx, args) => {
    return await queryHasActiveSubscription(ctx.db, args.creemCustomerId);
  },
});

export const getPayments = query({
  args: { creemCustomerId: v.string() },
  handler: async (ctx, args) => {
    return await queryPaymentsByCustomer(ctx.db, args.creemCustomerId);
  },
});

5. Set up the webhook handler

// convex/http.ts
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { handleCreemWebhook } from "creem-convex";

const http = httpRouter();

http.route({
  path: "/creem/webhook",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    return await handleCreemWebhook(ctx, request, {
      onCheckoutCompleted: async (ctx, data) => {
        await ctx.runMutation(internal.creem.syncPayment, { checkout: data });
      },
      onGrantAccess: async (ctx, data) => {
        await ctx.runMutation(internal.creem.syncSubscription, { subscription: data });
        // Add your own logic: unlock features, send welcome email, etc.
      },
      onRevokeAccess: async (ctx, data) => {
        await ctx.runMutation(internal.creem.syncSubscription, { subscription: data });
        // Add your own logic: lock features, send cancellation email, etc.
      },
    });
  }),
});

export default http;

6. Use in your React app

import { useQuery, useAction } from "convex/react";
import { api } from "../convex/_generated/api";

function PricingButton({ productId }: { productId: string }) {
  const checkout = useAction(api.creem.createCheckout);

  return (
    <button onClick={() => checkout({
      productId,
      successUrl: window.location.origin + "/success",
    })}>
      Subscribe Now
    </button>
  );
}

function Dashboard({ customerId }: { customerId: string }) {
  // Real-time! Updates instantly when webhook arrives
  const access = useQuery(api.creem.hasAccess, { creemCustomerId: customerId });
  const payments = useQuery(api.creem.getPayments, { creemCustomerId: customerId });

  if (!access?.hasAccess) return <PricingButton productId="prod_xxx" />;

  return (
    <div>
      <p>Status: {access.status}</p>
      <p>Payments: {payments?.length ?? 0}</p>
    </div>
  );
}

API Reference

Schema

import { creemTables } from "creem-convex/schema";

Exports 4 table definitions with indexes:

| Table | Key Indexes | |-------|------------| | creem_payments | by_checkout, by_customer, by_product, by_subscription | | creem_subscriptions | by_creem_id, by_customer, by_product, by_status | | creem_customers | by_creem_id, by_email | | creem_webhook_events | by_event_id, by_type |

CreemClient

import { CreemClient } from "creem-convex";

const client = new CreemClient({ apiKey: "creem_xxx", testMode: true });

| Method | Description | |--------|-------------| | getProduct(id) | Get product by ID | | listProducts(page, limit) | List products (paginated) | | createProduct(params) | Create a new product | | createCheckout(params) | Create checkout session → returns checkout_url | | getCheckout(id) | Get checkout by ID | | getSubscription(id) | Get subscription by ID | | cancelSubscription(id) | Cancel subscription | | pauseSubscription(id) | Pause subscription | | resumeSubscription(id) | Resume subscription | | getCustomer(id) | Get customer by ID | | getCustomerByEmail(email) | Get customer by email | | listCustomers(page, limit) | List customers (paginated) | | createBillingPortal(customerId) | Get billing portal URL |

Action Helpers

Pre-configured helpers that read CREEM_API_KEY and CREEM_TEST_MODE from environment:

import {
  getCreemClient,
  creemCreateCheckout,
  creemGetProduct,
  creemListProducts,
  creemGetSubscription,
  creemCancelSubscription,
  creemPauseSubscription,
  creemResumeSubscription,
  creemGetCustomer,
  creemCreateBillingPortal,
} from "creem-convex";

Mutation Helpers

Sync Creem webhook data to your Convex database:

import {
  syncCheckoutToDb,    // Sync checkout.completed → payments + customer + subscription
  syncSubscriptionToDb, // Sync subscription events → subscription + customer
  upsertCustomer,       // Upsert customer record
  upsertSubscription,   // Upsert subscription record
  logWebhookEvent,      // Idempotent event logging
} from "creem-convex";

Query Helpers

Real-time queries for your Convex functions:

import {
  querySubscriptionByCustomer,  // Latest subscription for customer
  querySubscriptionById,         // Subscription by Creem ID
  queryActiveSubscriptions,      // All active subscriptions
  querySubscriptionsByProduct,   // Subscriptions for a product
  queryPaymentsByCustomer,       // Payment history
  queryCustomerByEmail,          // Find customer by email
  queryHasActiveSubscription,    // { hasAccess, status, subscription }
  queryWebhookEvents,            // Recent webhook events
} from "creem-convex";

React Hooks

import {
  useCreemSubscription,  // Real-time subscription status
  useCreemAccess,         // Real-time { hasAccess, status }
  useCreemCheckout,       // { startCheckout, isLoading, error }
  useCreemPayments,       // Real-time payment list
} from "creem-convex/react";

Webhook Handler

import { handleCreemWebhook } from "creem-convex";

Supported event handlers:

| Handler | Triggered by | |---------|-------------| | onCheckoutCompleted | checkout.completed | | onSubscriptionActive | subscription.active | | onSubscriptionPaid | subscription.paid | | onSubscriptionCanceled | subscription.canceled | | onSubscriptionExpired | subscription.expired | | onSubscriptionTrialing | subscription.trialing | | onSubscriptionPaused | subscription.paused | | onSubscriptionPastDue | subscription.past_due | | onSubscriptionUpdate | subscription.update | | onSubscriptionScheduledCancel | subscription.scheduled_cancel | | onRefundCreated | refund.created | | onDisputeCreated | dispute.created | | onGrantAccess | subscription.active / .trialing / .paid | | onRevokeAccess | subscription.canceled / .expired / .paused |

The onGrantAccess and onRevokeAccess handlers simplify the most common pattern: deciding whether a user should have access to your product.

Testing

npm test              # Run all tests
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report

71 tests covering client, webhook verification, handler routing, mutations, queries, and schema.

Environments

| Mode | API URL | Use | |------|---------|-----| | Test | https://test-api.creem.io | Development, no real charges | | Live | https://api.creem.io | Production |

Set CREEM_TEST_MODE=true in your Convex environment for development.

License

MIT

Author

Manuel ReyesLatamFlows