payloadcms/payload · error · Forbidden

You are not allowed to perform this action.

Error message

You are not allowed to perform this action.

What it means

Thrown by the Stripe plugin's REST endpoint handler (`stripeREST`) as a `Forbidden(req.t)` when the request has no authenticated `user`. The message is the i18n translation key for 'You are not allowed to perform this action.' — the default `Forbidden` text. Any client hitting the Stripe proxy endpoint without a session gets this 403.

Source

Thrown at packages/plugin-stripe/src/routes/rest.ts:28

  pluginConfig: StripePluginConfig
  req: PayloadRequest
}): Promise<any> => {
  let responseStatus = 200
  let responseJSON

  const { pluginConfig, req } = args

  await addDataAndFileToRequest(req)

  const requestWithData = req
  const { data, payload, user } = requestWithData

  const { stripeSecretKey } = pluginConfig

  try {
    if (!user) {
      // TODO: make this customizable from the config
      throw new Forbidden(req.t)
    }

    responseJSON = await stripeProxy({
      stripeArgs: data?.stripeArgs, // example: ['cus_MGgt3Tuj3D66f2'] or [{ limit: 100 }, { stripeAccount: 'acct_1J9Z4pKZ4Z4Z4Z4Z' }]
      stripeMethod: data?.stripeMethod, // example: 'subscriptions.list',
      stripeSecretKey,
    })

    const { status } = responseJSON
    responseStatus = status
  } catch (error: unknown) {
    const message = `An error has occurred in the Stripe plugin REST handler: '${JSON.stringify(
      error,
    )}'`
    payload.logger.error(message)
    responseStatus = 500
    responseJSON = {
      message,

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Ensure the client is authenticated (valid session cookie or Authorization header) before calling the endpoint
  2. If server-to-server, pass a Payload API key or perform login first to obtain a session
  3. If you genuinely need anonymous Stripe access, mount a custom route with its own auth rather than relying on this guarded endpoint

Example fix

// before — no credentials
await fetch('/api/stripe', { method: 'POST', body: JSON.stringify({ stripeMethod: 'customers.list' }) })
// after — include the session cookie / API key
await fetch('/api/stripe', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json', Authorization: `users API-Key ${apiKey}` },
  body: JSON.stringify({ stripeMethod: 'customers.list', stripeArgs: [{ limit: 10 }] }),
})
Defensive patterns

Strategy: validation

Validate before calling

// Ensure a user session exists before calling the Stripe proxy
if (!currentUser) { await login(); }
await fetch('/api/stripe', { method: 'POST', credentials: 'include', body: JSON.stringify({ stripeMethod, stripeArgs }) })

Try / catch

const res = await fetch('/api/stripe', { method: 'POST', credentials: 'include', body })
if (res.status === 403) {
  // session missing or expired — re-authenticate, then retry
  await relogin()
  return fetch('/api/stripe', { method: 'POST', credentials: 'include', body })
}

Prevention

When it happens

Trigger: Calling the Stripe REST proxy endpoint (`/api/stripe` or whatever path the plugin mounts) without a logged-in Payload user; expired session cookie; the endpoint hit from a server-to-server script that did not set a Cookie/Authorization header.

Common situations: Frontend calling the Stripe proxy before login completes; cookie blocked by SameSite/secure policy; a cron/background job assuming the endpoint was public; proxying a test request from Postman without copying the session cookie.

Related errors


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