{"record":{"id":"dc86c59fc2b12bcf","repo":"juspay/hyperswitch","slug":"webhook-failed-with-error-code-response-body-e","errorCode":null,"errorMessage":"Webhook failed with error code \"${response.body?.error?.code}\" error message \"${response.body?.error?.message}\"","messagePattern":"Webhook failed with error code \"(.+?)\" error message \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cypress-tests/cypress/support/commands.js","lineNumber":8258,"sourceCode":"\n    const headers = {\n      \"Content-Type\": contentType,\n    };\n\n    const sendRequest = () =>\n      cy\n        .request({\n          method: \"POST\",\n          url: completeUrl,\n          headers,\n          body,\n          failOnStatusCode: false,\n        })\n        .then((response) => {\n          logRequestId(response.headers[\"x-request-id\"]);\n\n          if (response.status !== 200) {\n            throw new Error(\n              `Webhook failed with error code \"${response.body?.error?.code}\" error message \"${response.body?.error?.message}\"`\n            );\n          }\n\n          return cy.wrap(response);\n        });\n\n    // If signature required\n    if (webhookConfig.webhookSecret) {\n      const bodyString = JSON.stringify(webhookBody);\n      body = bodyString;\n\n      return cy\n        .task(\"hmac_sha256\", {\n          secret: webhookConfig.webhookSecret,\n          message: bodyString,\n        })\n        .then((signature) => {","sourceCodeStart":8240,"sourceCodeEnd":8276,"githubUrl":"https://github.com/juspay/hyperswitch/blob/9b8b89dc378b62c9a6feda647bad4719d2de7699/cypress-tests/cypress/support/commands.js#L8240-L8276","documentation":"This error is thrown by a custom Cypress support command that POSTs a simulated webhook delivery (completeUrl, optional headers, optional HMAC-signed body) with failOnStatusCode: false. After the request resolves it logs the x-request-id header and, when response.status !== 200, throws this Error embedding the server's error.code and error.message. So the message is a relay of the receiving webhook endpoint's own rejection, not a client-side network failure (cy.request would fail differently on DNS/connection errors).","triggerScenarios":"Any non-200 from the webhook endpoint: 401/403 when the signature (webhookSecret HMAC) is missing, wrong, or computed over a different byte sequence than the body actually sent; 404 when completeUrl points at a disabled or wrong-path route; 400 when the payload fails the endpoint's validation; 5xx when the downstream server errors. Also triggered when webhookSecret exists so the command signs the body, but the receiving side expects a different signing scheme or header name.","commonSituations":"Webhook secret rotated in the dashboard but not in the test env (or vice versa); signing JSON.stringify of a re-serialized object instead of the exact string sent; base URL pointing at the wrong environment/tenant where the webhook is not registered; webhook receiver disabled or the route changed in a newer server version; clock/timestamp skew if the endpoint validates a timestamp header.","solutions":["Read the embedded error code/message in the thrown string (and the x-request-id logged just before) to identify the server-side rejection reason, and grep server logs by that request id.","If 401/403 or a signature error: confirm webhookConfig.webhookSecret matches the currently registered secret, and that the HMAC is computed over the exact same string assigned to `body` (the command already does `const bodyString = JSON.stringify(webhookBody); body = bodyString;` — reuse bodyString for signing).","If 404: verify completeUrl — correct base URL for the environment, correct path, webhook route enabled for the profile/tenant.","If 400: inspect webhookBody against the endpoint's schema (required fields, types) and fix the payload in the test fixture.","If 5xx/502/503: retry after confirming service health; this is a server-side failure, not a test bug."],"exampleFix":"// before: signing one serialization, sending another\nconst signature = crypto.createHmac('sha256', secret).update(JSON.stringify(webhookBody)).digest('hex');\nlet body = webhookBody;\n\n// after: sign the exact bytes that go on the wire\nconst bodyString = JSON.stringify(webhookBody);\nconst signature = crypto.createHmac('sha256', secret).update(bodyString).digest('hex');\nconst body = bodyString;\n// headers carry the signature; request sends `body` unchanged","handlingStrategy":"validation","validationCode":"// Run before invoking the webhook command\nfunction validateWebhookCall(webhookConfig, webhookBody) {\n  const problems = [];\n  if (!webhookConfig?.url) problems.push('webhookConfig.url is empty — wrong env config');\n  if (typeof webhookBody !== 'object' || webhookBody === null) problems.push('webhookBody must be an object');\n  if (webhookConfig?.webhookSecret && typeof webhookConfig.webhookSecret !== 'string') {\n    problems.push('webhookSecret present but not a string');\n  }\n  if (problems.length) throw new Error(`Webhook pre-flight failed: ${problems.join('; ')}`);\n}\nvalidateWebhookCall(webhookConfig, webhookBody);","typeGuard":"// Narrows the response body so you branch on a known envelope instead of guessing\nfunction isApiErrorBody(body) {\n  return (\n    typeof body === 'object' &&\n    body !== null &&\n    'error' in body &&\n    typeof body.error === 'object' &&\n    body.error !== null &&\n    typeof body.error.code === 'string' &&\n    typeof body.error.message === 'string'\n  );\n}","tryCatchPattern":"// Cypress commands cannot be try/caught by the caller, so make the failure informative:\n// branch on status with expect() instead of a blind throw inside the command's .then().then((response) => {\n  if (response.status !== 200 && isApiErrorBody(response.body)) {\n    Cypress.log({ name: 'webhook', message: `code=${response.body.error.code} msg=${response.body.error.message}` });\n  }\n  expect(response.status, 'webhook status').to.eq(200);\n  return cy.wrap(response);\n});\n// If wrapping in a plain async helper instead of a cy command, the caller may use:\n// try { await postWebhook(...) } catch (e) { if (/Webhook failed with error code/.test(e.message)) { /* inspect code, decide retry */ } else throw e; }","preventionTips":["Keep the webhook secret in the test env in lockstep with the dashboard — rotate both together.","Always sign the exact byte string you send (assign body = bodyString once and reuse it for HMAC).","Pin completeUrl per environment in config rather than composing it ad hoc in tests.","Keep failOnStatusCode: false and branch on status so the x-request-id and error envelope reach your logs before any throw.","When the endpoint validates timestamps, use a clock-aware header and never cache signed payloads across runs."],"tags":["webhooks","hmac","cypress","http","api-testing"],"backgroundTag":null,"analyzedSha":"9b8b89dc378b62c9a6feda647bad4719d2de7699","analyzedAt":"2026-08-16T09:01:53.433Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}