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
- 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.
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
- 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.
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
- Server Action "${actionId}" was not found on the server. Re
- Could not determine origin for forwarded Server Actions requ
- Failed to link project ${linkRes.stdout} ${linkRes.stderr} (
- Failed to deploy project ${linkRes.stdout} ${linkRes.stderr}
- Next.js has blocked a javascript: URL as a security precauti
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/84adcf1d2bf30328.
Report an issue: GitHub.