{"record":{"id":"1882e284ebf3ca92","repo":"remotion-dev/remotion","slug":"signatures-do-not-match","errorCode":null,"errorMessage":"Signatures do not match","messagePattern":"Signatures do not match","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lambda-client/src/validate-webhook-signature.ts","lineNumber":40,"sourceCode":"\t\t\t\"No 'body' was provided to validateWebhookSignature().\",\n\t\t);\n\t}\n\n\tif (typeof require === 'undefined') {\n\t\tthrow new Error('validateWebhookSignature can only be called from Node.JS');\n\t}\n\n\tconst Crypto = require('crypto');\n\n\tconst hmac = Crypto.createHmac('sha512', secret);\n\tconst signature = `sha512=${hmac.update(JSON.stringify(body)).digest('hex')}`;\n\n\tif (!signatureHeader || signatureHeader === 'NO_SECRET_PROVIDED') {\n\t\tthrow new Error('No webhook signature was provided');\n\t}\n\n\tif (signatureHeader !== signature) {\n\t\tthrow new Error('Signatures do not match');\n\t}\n};\n","sourceCodeStart":22,"sourceCodeEnd":43,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/lambda-client/src/validate-webhook-signature.ts#L22-L43","documentation":"Thrown by validateWebhookSignature() when the incoming signatureHeader does not equal the HMAC computed over the body and the shared secret. A mismatch means the request was not authentic, was tampered with, or used a different secret.","triggerScenarios":"The body the receiver parsed differs from the raw body Remotion signed (most common), the secret on the receiver differs from the one passed to renderMediaOnLambda(), or the request was replayed/tampered.","commonSituations":"Express re-serializing req.body with different key order / whitespace than the raw JSON Remotion used to compute the HMAC; a typo in the secret; checking the header before body-parser has run; an attacker actually tampering.","solutions":["Capture and HMAC the RAW request body (the exact bytes Remotion signed), not a re-serialized object — use express.json({verify}) or a raw-body middleware.","Confirm the secret string matches byte-for-byte what was passed to renderMediaOnLambda() (no trailing newline, same encoding).","Reject the request with 401 on signature mismatch and log nothing sensitive."],"exampleFix":"// before (re-serialized body, drifts from signed bytes)\napp.post('/webhook', express.json(), (req, res) => {\n  validateWebhookSignature({secret, body: req.body, signatureHeader: req.headers['x-remotion-signature']});\n});\n\n// after (capture raw body)\napp.post('/webhook', express.json({verify: (req, _res, buf) => { req.rawBody = buf; }}), (req, res) => {\n  validateWebhookSignature({secret, body: JSON.parse(req.rawBody), signatureHeader: req.headers['x-remotion-signature']});\n});","handlingStrategy":"try-catch","validationCode":"// Capture the RAW request body — the exact bytes Remotion signed.\napp.post('/webhook', express.json({verify: (req, _res, buf) => { req.rawBody = buf; }}), (req, res) => {\n  try {\n    validateWebhookSignature({secret, body: JSON.parse(req.rawBody), signatureHeader: req.headers['x-remotion-signature']});\n    res.status(200).send('ok');\n  } catch {\n    res.status(401).send('unauthorized');\n  }\n});","typeGuard":null,"tryCatchPattern":"try {\n  validateWebhookSignature({secret, body, signatureHeader});\n} catch (err) {\n  // Do NOT distinguish 'Signatures do not match' from other failures in the response.\n  return res.status(401).send('unauthorized');\n}","preventionTips":["Verify the RAW body, not a re-serialized object — use express.json({verify}) or a raw-body middleware.","Make sure the secret on the validator side matches the one passed to renderMediaOnLambda().","Return a generic 401 on any failure; never reveal which signature check failed."],"tags":["lambda","webhook","security","hmac"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}