obsidian/wiki/payloadcms/plugin-stripe.md
2026-05-15 16:27:02 +01:00

5.1 KiB
Raw Permalink Blame History

title aliases tags sources created updated
Stripe Plugin
payload-stripe
plugin-stripe
stripe-payments-payload
payloadcms
stripe
payments
ecommerce
plugin
webhooks
sync
raw/plugins__stripe.md
2026-05-15 2026-05-15

Stripe Plugin

Integrates Stripe billing into Payload CMS — two-way sync, webhook handling, and a proxied Stripe REST API behind Payload access control.

Installation

pnpm add @payloadcms/plugin-stripe

Basic Config

import { stripePlugin } from '@payloadcms/plugin-stripe'

buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
    }),
  ],
})

Options

Option Type Default Description
stripeSecretKey * string Stripe secret key
stripeWebhooksEndpointSecret string Webhook signing secret
rest boolean false Open /api/stripe/rest proxy (dev only)
webhooks object | function Webhook event handlers
sync array Auto-sync configs between collections and Stripe resources
logs boolean false Log sync events to console

Auto-Opened Endpoints

Endpoint Method Description
/api/stripe/rest POST Proxied Stripe REST API (behind Payload access control)
/api/stripe/webhooks POST Stripe webhook receiver

Webhooks Setup

Dev:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhooks
# paste the secret into .env as STRIPE_WEBHOOKS_ENDPOINT_SECRET

Production: Create webhook in Stripe dashboard → set URL to YOUR_DOMAIN/api/stripe/webhooks → paste secret into .env.

Handler config:

stripePlugin({
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
  webhooks: {
    'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
      // handle event
    },
  },
})

Serverless Warning

Stripe expects a 2xx response within 1020 seconds. The plugin processes webhooks asynchronously — in serverless environments the function instance may close before processing completes, causing duplicate events.

Vercel fix: install @vercel/functions — the plugin auto-detects it and wraps handlers in waitUntil().

Sync — Two-Way Auto-Sync

Maps Payload collection fields to Stripe resource properties. Hooks + webhook handlers are created automatically.

stripePlugin({
  sync: [
    {
      collection: 'customers',
      stripeResourceType: 'customers',
      stripeResourceTypeSingular: 'customer',
      fields: [
        { fieldPath: 'name', stripeProperty: 'name' },
      ],
    },
  ],
})

What sync does automatically:

  • Adds read-only stripeID field (Stripe-generated cross-reference)
  • Adds direct link to resource on Stripe.com in admin
  • Adds read-only skipSync flag to prevent infinite loops
  • Attaches hooks: beforeValidate: createNewInStripe, beforeChange: syncExistingWithStripe, afterDelete: deleteFromStripe
  • Handles webhooks: STRIPE_TYPE.created/updated/deleted

Limitation: Only top-level fields supported (Stripe API constraint).

Server-Side Usage

Prefer direct Stripe SDK on server rather than the REST proxy:

import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2022-08-01' })
const customer = await stripe.customers.create({ email: data.email })

Or use stripeProxy from the plugin:

import { stripeProxy } from '@payloadcms/plugin-stripe'
const customer = await stripeProxy({
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeMethod: 'customers.create',
  stripeArgs: [{ email: data.email }],
})

TypeScript

import { StripeConfig, StripeWebhookHandler, StripeProxy } from '@payloadcms/plugin-stripe/types'

Key Takeaways

  • Hides Stripe credentials — client-side calls go through Payload's access control, never directly to Stripe
  • REST proxy (rest: true) is dev-only — opening it in production gives authenticated users full Stripe API access
  • Two-way sync requires webhooks — set stripeWebhooksEndpointSecret or changes from Stripe won't propagate to Payload
  • Serverless + webhooks = @vercel/functions — without it, async webhook handlers may be killed before completing
  • sync only works on top-level Stripe fields — nested fields require manual hooks
  • skipSync flag prevents infinite loops — sync hooks set it before triggering webhooks, webhooks check it before re-syncing
  • For e-commerce, pair with wiki/payloadcms/ecommerce or wiki/payloadcms/ecommerce-payment-adapters

Sources