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

@rivium/pay-nodejs-sdk

v0.1.0

Published

RiviumPay SDK for Node.js - Provider-agnostic payments with Lemon Squeezy support (subscriptions, checkouts, webhooks, signature verification)

Readme

@rivium/pay-nodejs-sdk

RiviumPay SDK for Node.js — provider-agnostic payments with subscriptions, hosted checkouts, and signed webhook verification.

Lemon Squeezy is the first supported provider. The interface is designed so additional providers (Stripe, Paddle, NowPayments, …) can plug in behind the same PaymentProvider contract without consumer code needing to change.

Installation

npm install @rivium/pay-nodejs-sdk

Requires Node.js ≥ 18 (uses native fetch and crypto). Zero runtime dependencies.

Quick start

import { LemonSqueezyProvider } from '@rivium/pay-nodejs-sdk';

const provider = new LemonSqueezyProvider({
  apiKey: process.env.LS_API_KEY!,
  webhookSecret: process.env.LS_WEBHOOK_SECRET!,
  storeId: process.env.LS_STORE_ID!,
});

// 1. Create a checkout session for a customer.
const checkout = await provider.createCheckout({
  variantId: 'starter-monthly', // your LS variant id
  customerEmail: '[email protected]',
  customerName: 'Bester Realty',
  redirectUrl: 'https://yourapp.com/billing/success',
  customData: { companyId: 'abc123' }, // travels back in webhook events
});

// Redirect the customer's browser to checkout.url.

Handling webhooks

Webhook signature verification must happen against the raw, unparsed request body. If your framework parses JSON before your handler runs, the signature check will fail.

Express

import express from 'express';
import {
  LemonSqueezyProvider,
  SignatureVerificationError,
} from '@rivium/pay-nodejs-sdk';

const app = express();

// IMPORTANT: register raw-body parsing for the webhook route BEFORE
// any global express.json() middleware.
app.post(
  '/webhooks/lemon-squeezy',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      const event = await provider.verifyAndParseWebhook(
        req.body, // Buffer
        req.headers['x-signature'] as string,
      );

      if (!event) {
        // LS event that doesn't map to a canonical type we care about.
        // 2xx so LS stops retrying.
        return res.status(200).send();
      }

      // Persist whatever your app needs to track. Use event.eventId as
      // an idempotency key — LS retries failed deliveries.
      await persist(event);

      res.status(200).send();
    } catch (err) {
      if (err instanceof SignatureVerificationError) {
        // 401 so LS keeps retrying. Fix the secret on your side and the
        // retries will succeed without re-firing the event from LS.
        return res.status(401).send('Invalid signature');
      }
      // Any other error: 500 so LS retries. Don't 2xx a real failure.
      console.error('[webhook] error', err);
      res.status(500).send();
    }
  },
);

// global JSON parser AFTER — so it doesn't interfere with the webhook route.
app.use(express.json());

Canonical event types

The provider normalizes Lemon Squeezy's ~15 native event names onto a small canonical set:

| event.type | Triggers from LS events | | ----------------------------- | ------------------------------------------------------------------- | | subscription.created | subscription_created | | subscription.updated | subscription_updated, subscription_resumed, subscription_plan_changed, subscription_paused, subscription_unpaused | | subscription.cancelled | subscription_cancelled, subscription_expired | | payment.succeeded | subscription_payment_success | | payment.failed | subscription_payment_failed |

Native LS events outside this list (refunds, license keys, raw orders) return null from verifyAndParseWebhook. Consumer code should 2xx those so LS doesn't retry.

Subscription status

type SubscriptionStatus =
  | 'active'      // paid, in good standing
  | 'trialing'    // inside free trial
  | 'past_due'    // last payment failed; retries pending
  | 'cancelled'   // explicit cancel; service may still run until period end
  | 'expired'     // period ended without renewal
  | 'incomplete'; // created but never confirmed

The cancelAtPeriodEnd flag distinguishes "cancelled now, expires later" from "expired right now". Your service stays available while cancelAtPeriodEnd === true and currentPeriodEnd is in the future.

API surface

provider.createCheckout(input): Promise<CheckoutSession>
provider.getSubscription(id): Promise<PaymentSubscription | null>
provider.getCustomer(id): Promise<PaymentCustomer | null>
provider.cancelSubscription(id, { immediate? }): Promise<PaymentSubscription>
provider.resumeSubscription(id): Promise<PaymentSubscription>
provider.verifyAndParseWebhook(rawBody, signature): Promise<WebhookEvent | null>

cancelSubscription({ immediate: true }) throws — Lemon Squeezy doesn't support immediate hard-cancel via the public API. Cancel without immediate schedules for period end.

Errors

import {
  RiviumPayError,
  SignatureVerificationError,
  InvalidInputError,
  ProviderApiError,
} from '@rivium/pay-nodejs-sdk';
  • SignatureVerificationError — webhook signature didn't match. Respond 4xx.
  • InvalidInputError — missing required input from your code (e.g. no signature header).
  • ProviderApiError — the provider's API rejected the call. .statusCode and .cause carry the original.
  • RiviumPayError — base class for everything above. Catch this if you only need to distinguish package errors from runtime errors.

License

MIT

Support

  • Landing Page: https://rivium.co/cloud/rivium-pay
  • Documentation: https://rivium.co/cloud/rivium-pay/docs/sdks-nodejs
  • Issues: https://github.com/Rivium-co/rivium-pay-nodejs-sdk/issues
  • Email: [email protected]