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

@xenterprises/fastify-xstripe

v1.0.0

Published

Fastify plugin for Stripe webhooks with simplified, testable handlers for subscription events.

Readme

xStripe

Fastify v5 plugin for simplified, testable Stripe webhook handling. Focuses on subscription lifecycle management with clean, readable code.

Requirements

  • Fastify v5.0.0+
  • Node.js v20+

Features

  • Simple webhook handling - Clean event-based architecture
  • Built-in handlers - 20+ default handlers for common events
  • Easy to test - Handlers are pure functions
  • Type-safe - Full TypeScript support
  • Readable - Clear, self-documenting code
  • Flexible - Override any default handler
  • Production-ready - Signature verification, error handling, logging

Installation

npm install @xenterprises/fastify-xstripe fastify@5

Quick Start

import Fastify from 'fastify';
import xStripe from '@xenterprises/fastify-xstripe';

const fastify = Fastify({ logger: true });

await fastify.register(xStripe, {
  apiKey: process.env.STRIPE_API_KEY,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  webhookPath: '/stripe/webhook', // Optional, defaults to /stripe/webhook
});

await fastify.listen({ port: 3000 });

Custom Handlers

Override default handlers with your business logic:

await fastify.register(xStripe, {
  apiKey: process.env.STRIPE_API_KEY,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  handlers: {
    // Handle new subscription
    'customer.subscription.created': async (event, fastify, stripe) => {
      const subscription = event.data.object;

      // Update your database
      await fastify.prisma.user.update({
        where: { stripeCustomerId: subscription.customer },
        data: {
          subscriptionId: subscription.id,
          subscriptionStatus: subscription.status,
          planId: subscription.items.data[0]?.price.id,
        },
      });

      // Send welcome email
      await fastify.email.send(
        subscription.customer.email,
        'Welcome to Premium!',
        '<h1>Thanks for subscribing!</h1>'
      );
    },

    // Handle subscription cancellation
    'customer.subscription.deleted': async (event, fastify, stripe) => {
      const subscription = event.data.object;

      // Revoke access
      await fastify.prisma.user.update({
        where: { stripeSubscriptionId: subscription.id },
        data: {
          subscriptionStatus: 'canceled',
          hasAccess: false,
        },
      });
    },

    // Handle failed payment
    'invoice.payment_failed': async (event, fastify, stripe) => {
      const invoice = event.data.object;

      // Send payment failure email
      const customer = await stripe.customers.retrieve(invoice.customer);
      await fastify.email.send(
        customer.email,
        'Payment Failed',
        '<p>Please update your payment method.</p>'
      );
    },
  },
});

Handler Function Signature

All handlers receive three parameters:

async function handler(event, fastify, stripe) {
  // event - The Stripe webhook event object
  // fastify - The Fastify instance (access to decorators)
  // stripe - The Stripe client instance
}

Supported Events

Subscription Events

  • customer.subscription.created - New subscription
  • customer.subscription.updated - Subscription changed
  • customer.subscription.deleted - Subscription canceled
  • customer.subscription.trial_will_end - Trial ending in 3 days
  • customer.subscription.paused - Subscription paused
  • customer.subscription.resumed - Subscription resumed

Invoice Events

  • invoice.created - Invoice created
  • invoice.finalized - Invoice ready for payment
  • invoice.paid - Payment succeeded
  • invoice.payment_failed - Payment failed
  • invoice.upcoming - Upcoming charge notification

Payment Events

  • payment_intent.succeeded - Payment successful
  • payment_intent.payment_failed - Payment failed

Customer Events

  • customer.created - New customer
  • customer.updated - Customer details changed
  • customer.deleted - Customer deleted

Payment Method Events

  • payment_method.attached - Payment method added
  • payment_method.detached - Payment method removed

Checkout Events

  • checkout.session.completed - Checkout completed
  • checkout.session.expired - Checkout session expired

Testing Webhooks Locally

1. Use Stripe CLI

# Install Stripe CLI
brew install stripe/stripe-cli/stripe

# Login to Stripe
stripe login

# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/stripe/webhook

# Trigger test events
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed

2. Test Handlers Directly

Since handlers are pure functions, they're easy to test:

import { test } from 'node:test';
import assert from 'node:assert';

test('subscription.created handler', async () => {
  const mockEvent = {
    type: 'customer.subscription.created',
    data: {
      object: {
        id: 'sub_123',
        customer: 'cus_123',
        status: 'active',
      },
    },
  };

  const mockFastify = {
    log: { info: () => {} },
    prisma: {
      user: {
        update: async (data) => {
          assert.equal(data.where.stripeCustomerId, 'cus_123');
          return {};
        },
      },
    },
  };

  const mockStripe = {};

  await handlers['customer.subscription.created'](
    mockEvent,
    mockFastify,
    mockStripe
  );
});

Common Patterns

Update Database on Subscription Change

'customer.subscription.updated': async (event, fastify, stripe) => {
  const subscription = event.data.object;
  const previous = event.data.previous_attributes || {};

  // Check what changed
  if ('status' in previous) {
    await fastify.prisma.user.update({
      where: { stripeSubscriptionId: subscription.id },
      data: { subscriptionStatus: subscription.status },
    });

    // Handle specific status changes
    if (subscription.status === 'past_due') {
      // Send payment reminder
    }
  }
}

Send Notification Emails

'customer.subscription.trial_will_end': async (event, fastify, stripe) => {
  const subscription = event.data.object;
  const customer = await stripe.customers.retrieve(subscription.customer);

  await fastify.email.send(
    customer.email,
    'Your trial ends soon!',
    '<p>Convert to a paid plan to keep access.</p>'
  );
}

Track Failed Payments

'invoice.payment_failed': async (event, fastify, stripe) => {
  const invoice = event.data.object;

  await fastify.prisma.user.update({
    where: { stripeSubscriptionId: invoice.subscription },
    data: {
      failedPaymentCount: { increment: 1 },
      lastFailedPayment: new Date(),
    },
  });

  // After 3 failed payments, suspend account
  const user = await fastify.prisma.user.findUnique({
    where: { stripeSubscriptionId: invoice.subscription },
  });

  if (user.failedPaymentCount >= 3) {
    await fastify.prisma.user.update({
      where: { id: user.id },
      data: { accountSuspended: true },
    });
  }
}

Configuration Options

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | apiKey | string | Yes | - | Stripe API key | | webhookSecret | string | No | - | Stripe webhook signing secret | | webhookPath | string | No | /stripe/webhook | Webhook endpoint path | | handlers | object | No | {} | Custom event handlers | | apiVersion | string | No | 2024-11-20.acacia | Stripe API version |

Security

  • Signature Verification: All webhooks are verified using Stripe's signature
  • Raw Body Required: Plugin automatically handles raw body parsing
  • Error Isolation: Handler errors don't prevent webhook acknowledgment
  • Logging: All events and errors are logged

Best Practices

  1. Always acknowledge webhooks quickly - Do heavy processing async
  2. Make handlers idempotent - Stripe may send events multiple times
  3. Log everything - Use structured logging for debugging
  4. Test with Stripe CLI - Test all event types before production
  5. Monitor failed handlers - Set up alerts for handler errors

Environment Variables

STRIPE_API_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Integration with Other xPlugins

Works seamlessly with other x-series plugins:

await fastify.register(xStripe, { /* ... */ });
await fastify.register(xTwilio, { /* ... */ });  // Send SMS notifications
await fastify.register(xConfig, { /* ... */ });  // Email notifications

// In your handlers:
'invoice.payment_failed': async (event, fastify, stripe) => {
  // Send email via xConfig/SendGrid
  await fastify.email.send(/* ... */);

  // Send SMS via xTwilio
  await fastify.sms.send(/* ... */);
}

License

ISC