{"record":{"id":"84adcf1d2bf30328","repo":"vercel/next.js","slug":"invalid-server-action-payload-failed-to-decrypt","errorCode":null,"errorMessage":"Invalid Server Action payload: failed to decrypt.","messagePattern":"Invalid Server Action payload: failed to decrypt\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/next/src/server/app-render/encryption.ts","lineNumber":66,"sourceCode":"async function decodeActionBoundArg(actionId: string, arg: string) {\n  const key = await getActionEncryptionKey()\n  if (typeof key === 'undefined') {\n    throw new Error(\n      `Missing encryption key for Server Action. This is a bug in Next.js`\n    )\n  }\n\n  // Get the iv (16 bytes) and the payload from the arg.\n  const originalPayload = atob(arg)\n  const ivValue = originalPayload.slice(0, 16)\n  const payload = originalPayload.slice(16)\n\n  const decrypted = textDecoder.decode(\n    await decrypt(key, stringToUint8Array(ivValue), stringToUint8Array(payload))\n  )\n\n  if (!decrypted.startsWith(actionId)) {\n    throw new Error('Invalid Server Action payload: failed to decrypt.')\n  }\n\n  return decrypted.slice(actionId.length)\n}\n\n/**\n * Encrypt the serialized string with the action id as the salt. Add a prefix to\n * later ensure that the payload is correctly decrypted, similar to a checksum.\n */\nasync function encodeActionBoundArg(actionId: string, arg: string) {\n  const key = await getActionEncryptionKey()\n  if (key === undefined) {\n    throw new Error(\n      `Missing encryption key for Server Action. This is a bug in Next.js`\n    )\n  }\n\n  // Get 16 random bytes as iv.","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/vercel/next.js/blob/0ae8c72462952df163f1b1e0726641bc5b40dc93/packages/next/src/server/app-render/encryption.ts#L48-L84","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure all server instances share the same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY (do not change it without coordination).","After rotating the encryption key, force clients to reload so they fetch payloads encrypted with the new key.","If self-hosting, set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY explicitly and consistently across all replicas.","Confirm the deployment uses a single, stable key rather than auto-generated per-build keys when action payloads may outlive a deploy."],"exampleFix":"// before: servers use different keys -> decryption fails\n// server A: NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=keyA\n// server B: (unset, auto-generated)\n\n// after: pin a single shared key on all replicas\n// NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=<same-base64-key> on A and B","handlingStrategy":"validation","validationCode":"// Ensure a stable, shared encryption key across all replicas.\nconst KEY = process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY\nif (!KEY) throw new Error('Set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for action bound args')\n// validate it's stable base64\nif (Buffer.from(KEY, 'base64').toString('base64') !== KEY) {\n  throw new Error('Encryption key must be valid base64')\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pin NEXT_SERVER_ACTIONS_ENCRYPTION_KEY and keep it identical across all servers.","Coordinate key rotation with a client reload to invalidate stale payloads.","Never auto-generate per-build keys when action payloads may outlive a deploy.","Document the key requirement in deployment guides."],"tags":["server-actions","encryption","security","deployment"],"analyzedSha":"0ae8c72462952df163f1b1e0726641bc5b40dc93","analyzedAt":"2026-08-06T19:44:29.143Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}