vercel/next.js · error · Error

Invalid Server Action payload: failed to decrypt.

Error message

Invalid Server Action payload: failed to decrypt.

What it means

Thrown by decodeActionBoundArg() after AES-GCM decryption of a bound (closure) Server Action argument. The decrypted plaintext must begin with the actionId used as a salt/checksum prefix; if it does not, the payload was encrypted with a different key or is corrupt/tampered. This guards bound action arguments (e.g. captured closure values) that are encrypted client-side and decrypted server-side.

Source

Thrown at packages/next/src/server/app-render/encryption.ts:66

async function decodeActionBoundArg(actionId: string, arg: string) {
  const key = await getActionEncryptionKey()
  if (typeof key === 'undefined') {
    throw new Error(
      `Missing encryption key for Server Action. This is a bug in Next.js`
    )
  }

  // Get the iv (16 bytes) and the payload from the arg.
  const originalPayload = atob(arg)
  const ivValue = originalPayload.slice(0, 16)
  const payload = originalPayload.slice(16)

  const decrypted = textDecoder.decode(
    await decrypt(key, stringToUint8Array(ivValue), stringToUint8Array(payload))
  )

  if (!decrypted.startsWith(actionId)) {
    throw new Error('Invalid Server Action payload: failed to decrypt.')
  }

  return decrypted.slice(actionId.length)
}

/**
 * Encrypt the serialized string with the action id as the salt. Add a prefix to
 * later ensure that the payload is correctly decrypted, similar to a checksum.
 */
async function encodeActionBoundArg(actionId: string, arg: string) {
  const key = await getActionEncryptionKey()
  if (key === undefined) {
    throw new Error(
      `Missing encryption key for Server Action. This is a bug in Next.js`
    )
  }

  // Get 16 random bytes as iv.

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Ensure all server instances share the same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY (do not change it without coordination).
  2. After rotating the encryption key, force clients to reload so they fetch payloads encrypted with the new key.
  3. If self-hosting, set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY explicitly and consistently across all replicas.
  4. Confirm the deployment uses a single, stable key rather than auto-generated per-build keys when action payloads may outlive a deploy.

Example fix

// before: servers use different keys -> decryption fails
// server A: NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=keyA
// server B: (unset, auto-generated)

// after: pin a single shared key on all replicas
// NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=<same-base64-key> on A and B
Defensive patterns

Strategy: validation

Validate before calling

// Ensure a stable, shared encryption key across all replicas.
const KEY = process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
if (!KEY) throw new Error('Set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for action bound args')
// validate it's stable base64
if (Buffer.from(KEY, 'base64').toString('base64') !== KEY) {
  throw new Error('Encryption key must be valid base64')
}

Prevention

When it happens

Trigger: A Server Action with bound arguments is invoked. decodeActionBoundArg decrypts the arg using the action encryption key (from NEXT_SERVER_ACTIONS_ENCRYPTION_KEY or the manifest) and checks that the result starts with actionId. A key mismatch (different deployment/secret) or a tampered payload causes the prefix check to fail.

Common situations: NEXT_SERVER_ACTIONS_ENCRYPTION_KEY changed between the build that generated the encrypted payload and the server decrypting it; a user submits an action payload from a previous deployment with a different encryption key; load-balanced servers using different keys; manual rotation of the encryption key without invalidating old sessions.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/84adcf1d2bf30328. Report an issue: GitHub.