gchq/CyberChef · error · OperationError

Unable to decode JSON payload: ${e.message}

Error message

Unable to decode JSON payload: ${e.message}

What it means

Thrown after the signature has already validated successfully, when JSON.parse(payloadJson) fails on the decoded Base64 payload. This means the cookie is authentic but its payload is not valid JSON, which can happen with non-JSON serialization, encoding issues, or (rarely) a correctly-signed but corrupt payload. The original parse error message is appended for diagnostics.

Source

Thrown at src/core/operations/FlaskSessionVerify.mjs:129

            throw new OperationError("Invalid signature!");
        }

        try {
            const decoded = JSON.parse(payloadJson);
            if (!args[3]) {
                return {
                    valid: true,
                    payload: decoded,
                };
            } else {
                return {
                    valid: true,
                    payload: decoded,
                    timestamp: timestamp
                };
            }
        } catch (e) {
            throw new OperationError("Unable to decode JSON payload: " + e.message);
        }

    }
}


export default FlaskSessionVerify;

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Inspect the decoded payload bytes manually (decode parts[0] as URL-safe Base64) to see the actual content.
  2. If the app uses a custom serializer, decode the payload with that serializer instead of JSON.parse.
  3. Confirm the cookie was produced by Flask's default session interface and not a third-party signer.
  4. Check for double-encoding or transport corruption of the cookie value.

Example fix

// before: relying on JSON.parse of a custom-serialized payload
const decoded = JSON.parse(payloadJson); // throws
// after: inspect raw bytes first
const raw = Buffer.from(parts[0].replace(/-/g,'+').replace(/_/g,'/'), 'base64').toString('utf8');
console.log(raw); // determine actual format before parsing
Defensive patterns

Strategy: try-catch

Validate before calling

// After signature passes, check the payload parses as JSON before relying on it
const raw = Buffer.from(parts[0].replace(/-/g,'+').replace(/_/g,'/'),'base64').toString('utf8');
let payload;
try { payload = JSON.parse(raw); } catch { /* not JSON; inspect raw */ }

Type guard

function isJsonString(s) {
  try { JSON.parse(s); return true; } catch { return false; }
}

Try / catch

try {
  const result = flaskVerify.run(cookie, args);
} catch (e) {
  if (e.type === 'OperationError' && /Unable to decode JSON payload/.test(e.message)) {
    // authentic cookie but non-JSON payload; decode manually with the app's serializer
  } else throw e;
}

Prevention

When it happens

Trigger: Signature passes but the decoded payload bytes are not a JSON document; payload was serialized with a custom Flask JSON provider (e.g. compact/non-standard); the Base64 decoding produced mojibake due to a padding/encoding edge case; or an itsdangerous serializer other than the default JSON serializer was used.

Common situations: Flask app configured with a custom JSON serializer (e.g. TaggedJSONSerializer producing non-raw-JSON bytes); legacy itsdangerous versions with different defaults; payload containing binary that survived signing but is not UTF-8 JSON.

Understand the failure class

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/411bc394232e6125. Report an issue: GitHub.