payloadcms/payload · critical · Error

Stripe secret key is required.

Error message

Stripe secret key is required.

What it means

Thrown at the top of the Stripe initiatePayment adapter when props.secretKey is falsy. The Stripe SDK cannot be instantiated without a secret key, so initiate refuses to start. This is the initiate-side mirror of the confirmOrder secret-key check: a configuration error meaning the stripe adapter was registered without a secretKey.

Source

Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts:26

  appInfo?: Stripe.StripeConfig['appInfo']
  secretKey: StripeAdapterArgs['secretKey']
}

export const initiatePayment: (props: Props) => NonNullable<PaymentAdapter>['initiatePayment'] =
  (props) =>
  async ({ data, req, transactionsSlug }) => {
    const payload = req.payload
    const { apiVersion, appInfo, secretKey } = props || {}

    const customerEmail = data.customerEmail
    const currency = data.currency
    const cart = data.cart
    const amount = cart.subtotal
    const billingAddressFromData = data.billingAddress
    const shippingAddressFromData = data.shippingAddress

    if (!secretKey) {
      throw new Error('Stripe secret key is required.')
    }

    if (!currency) {
      throw new Error('Currency is required.')
    }

    if (!cart || !cart.items || cart.items.length === 0) {
      throw new Error('Cart is empty or not provided.')
    }

    if (!customerEmail || typeof customerEmail !== 'string') {
      throw new Error('A valid customer email is required to make a purchase.')
    }

    if (!amount || typeof amount !== 'number' || amount <= 0) {
      throw new Error('A valid amount is required to initiate a payment.')
    }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Set STRIPE_SECRET_KEY in every environment (dev/staging/prod) and load it before the config is built.
  2. Confirm the plugin reads that exact variable name and pass secretKey explicitly when constructing the adapter.
  3. Fail fast at boot: in payload.config.ts throw if !process.env.STRIPE_SECRET_KEY.
  4. Use Stripe test keys (sk_test_...) in non-prod so the flow is exercised in CI.

Example fix

// before
stripeAdapter({ secretKey: process.env.STRIPE_KEY }) // unset
// after
const secretKey = process.env.STRIPE_SECRET_KEY
if (!secretKey) throw new Error('STRIPE_SECRET_KEY is required')
stripeAdapter({ secretKey })
Defensive patterns

Strategy: validation

Validate before calling

function resolveStripeSecretKey(): string {
  const key = process.env.STRIPE_SECRET_KEY
  if (!key) throw new Error('STRIPE_SECRET_KEY is required to initiate Stripe payments')
  return key
}

// register the adapter once at boot with the resolved key
stripePlugin({ stripe: { secretKey: resolveStripeSecretKey() } })

Type guard

export function hasSecretKey(props: unknown): props is { secretKey: string } {
  return typeof (props as { secretKey?: unknown })?.secretKey === 'string' && (props as { secretKey: string }).secretKey.length > 0
}

Try / catch

try {
  await payments.initiatePayment({ data })
} catch (err) {
  if (err instanceof Error && err.message === 'Stripe secret key is required.') {
    // misconfiguration — do not retry; surface to ops
    return { ok: false, reason: 'misconfigured-stripe' }
  }
  throw err
}

Prevention

When it happens

Trigger: The ecommerce stripe adapter is registered with secretKey undefined/empty (e.g. secretKey: process.env.STRIPE_SECRET_KEY where the env var is unset) and initiatePayment is then called on that adapter.

Common situations: STRIPE_SECRET_KEY not set in the deployed environment; .env file missing or not loaded by the runtime; env var name mismatch between config and deployment; local dev without a Stripe test key; the key is set only for confirmOrder's code path via a different adapter instance.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/e0c7a6bbd2e72fc1. Report an issue: GitHub.