X Enterprises

fastify-xstripe

Fastify plugin for Stripe webhook handling with signature verification, 23 default subscription event handlers, and the Stripe SDK client decorated on the instance.

fastify-xstripe

Stripe webhook handling for Fastify v5. Registers a POST webhook route with automatic signature verification, dispatches to 23 built-in subscription/invoice/payment/checkout/charge event handlers (all overridable), and decorates fastify.stripe with the full Stripe SDK client.

Installation

npm install @xenterprises/fastify-xstripe stripe

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,
});

// Full Stripe SDK available anywhere
const customer = await fastify.stripe.customers.create({ email: "user@example.com" });

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

Options

NameTypeDefaultRequiredDescription
apiKeystringYesStripe secret API key (sk_test_... or sk_live_...).
webhookSecretstringYesStripe webhook signing secret (whsec_...).
webhookPathstring"/stripe/webhook"NoPath where the webhook POST route is registered.
handlersobject{}NoCustom event handlers that override the defaults.
apiVersionstring"2024-11-20.acacia"NoStripe API version string.

Decorated Properties

PropertyTypeDescription
fastify.stripeStripeThe initialized Stripe SDK client — use it to call any Stripe API.

Pages

  • Webhook Route — The POST /stripe/webhook handler: signature verification, dispatch, custom handler authoring, and testing.
  • Helpers — Utility functions for formatting amounts, resolving subscription state, extracting invoice data, and more.

Custom Handlers

Override any default handler with your business logic. Each handler receives (event, fastify, stripe):

await fastify.register(xStripe, {
  apiKey: process.env.STRIPE_API_KEY,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  handlers: {
    "customer.subscription.created": async (event, fastify, stripe) => {
      const sub = event.data.object;
      await db.users.update({
        where: { stripeCustomerId: sub.customer },
        data: { subscriptionId: sub.id, status: sub.status },
      });
    },
    "invoice.payment_failed": async (event, fastify, stripe) => {
      const invoice = event.data.object;
      const customer = await stripe.customers.retrieve(invoice.customer);
      await sendEmail(customer.email, "Payment Failed", "Please update your card.");
    },
  },
});

Default Event Handlers

All 23 built-in handlers log structured data via fastify.log. Override any via the handlers option.

Subscription Events

EventLogged Fields
customer.subscription.createdsubscriptionId, customerId, status, planId
customer.subscription.updatedsubscriptionId, customerId, status, previous changes
customer.subscription.deletedsubscriptionId, customerId, canceledAt
customer.subscription.trial_will_endsubscriptionId, customerId, trialEnd
customer.subscription.pausedsubscriptionId, customerId
customer.subscription.resumedsubscriptionId, customerId

Invoice Events

EventLogged Fields
invoice.createdinvoiceId, customerId, amount, status
invoice.finalizedinvoiceId, customerId, amount
invoice.paidinvoiceId, customerId, subscriptionId, amount
invoice.payment_failedinvoiceId, customerId, amount, attemptCount (warn)
invoice.upcomingcustomerId, subscriptionId, amount, periodEnd

Payment Events

EventLogged Fields
payment_intent.succeededpaymentIntentId, customerId, amount, currency
payment_intent.payment_failedpaymentIntentId, customerId, amount, lastPaymentError (warn)

Customer Events

EventLogged Fields
customer.createdcustomerId, email
customer.updatedcustomerId, previous changes
customer.deletedcustomerId

Payment Method Events

EventLogged Fields
payment_method.attachedpaymentMethodId, customerId, type
payment_method.detachedpaymentMethodId, type

Checkout Events

EventLogged Fields
checkout.session.completedsessionId, customerId, subscriptionId, mode, paymentStatus
checkout.session.expiredsessionId

Charge Events

EventLogged Fields
charge.succeededchargeId, customerId, amount, currency, paymentMethod
charge.failedchargeId, customerId, amount, failureCode, failureMessage (error)
charge.refundedchargeId, customerId, amountRefunded, refundCount

Helper Utilities

Import from @xenterprises/fastify-xstripe/helpers or via named export:

import { helpers } from "@xenterprises/fastify-xstripe";
HelperSignatureDescription
formatAmount(amount, currency)(number, string) => stringFormat Stripe amount to currency string, e.g. 2000, "USD""$20.00".
getPlanName(subscription)(sub) => stringGet the plan name from a subscription object.
isActiveSubscription(subscription)(sub) => booleanCheck if subscription status is "active" or "trialing".
isInTrial(subscription)(sub) => booleanCheck if subscription is in trial period.
getDaysUntilTrialEnd(subscription)(sub) => numberDays remaining in trial.
isRenewal(event)(event) => booleanCheck if an invoice event is a renewal.
calculateMRR(subscription)(sub) => numberCalculate MRR in cents.
getSubscriptionStatusText(status)(string) => stringHuman-readable status, e.g. "active""Active".
getEventDescription(event)(event) => stringHuman-readable event description.
getCustomerEmail(event, stripe)(event, stripe) => Promise<string>Resolve customer email from event.
isTestEvent(event)(event) => booleanCheck if event is from test mode.
getMetadata(event)(event) => objectExtract metadata from event.
getPaymentMethodType(paymentMethod)(pm) => stringHuman-readable payment method type, e.g. "Card".
getInvoiceLineItems(invoice)(invoice) => arrayGet line items from invoice.
isSubscriptionInvoice(invoice)(invoice) => booleanCheck if invoice is subscription-related.
getNextBillingDate(subscription)(sub) => DateGet next billing date as a Date object.
formatDate(timestamp)(number) => stringFormat Unix timestamp to readable date string.

Error Reference

All errors use the [xStripe] prefix.

Startup Errors

ErrorCause
[xStripe] apiKey is required and must be a stringMissing or non-string apiKey.
[xStripe] webhookSecret is required and must be a stringMissing or non-string webhookSecret.
[xStripe] webhookPath must be a stringwebhookPath is not a string.
[xStripe] handlers must be a plain objecthandlers is not a plain object or is an array.
[xStripe] apiVersion must be a stringapiVersion is not a string.

Webhook Runtime Errors (HTTP 400)

ErrorCause
[xStripe] Missing stripe-signature headerWebhook request received without a signature header.
[xStripe] Webhook signature verification failed: ...Invalid or tampered webhook signature.

Environment Variables

VariableRequiredDescription
STRIPE_API_KEYYesStripe secret key (sk_test_... or sk_live_...). Pass as apiKey option.
STRIPE_WEBHOOK_SECRETYesWebhook signing secret from Stripe Dashboard or CLI (whsec_...). Pass as webhookSecret option.

How It Works

On registration, the plugin validates all options, initializes the Stripe SDK with the provided apiKey and apiVersion, and decorates fastify.stripe. A POST route is registered at webhookPath that reads the raw request body, verifies the Stripe signature using stripe.webhooks.constructEvent(), then dispatches the event to the resolved handler — user handlers are merged over defaults via object spread ({ ...defaultHandlers, ...handlers }). If a handler throws, the error is logged but the route still returns HTTP 200 to prevent Stripe from retrying the event. The fastify.stripe decorator gives full programmatic access to the Stripe SDK for any API call outside the webhook flow.

Testing Webhooks Locally

# Install Stripe CLI and 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
stripe trigger checkout.session.completed

AI Context

package: "@xenterprises/fastify-xstripe"
type: fastify-plugin
use-when: Stripe webhook handling with signature verification and full Stripe SDK access
decorator: fastify.stripe (full Stripe SDK client)
webhook-route: POST /stripe/webhook (configurable via webhookPath)
default-handlers: 23 built-in handlers for subscription/invoice/payment/customer/checkout/charge events — all overridable
env: STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET
helper-exports: formatAmount, getPlanName, isActiveSubscription, isInTrial, getDaysUntilTrialEnd, calculateMRR, and more
Copyright © 2026