juspay/hyperswitch · error · Error

Webhook failed with error code "${response.body?.error?.code

Error message

Webhook failed with error code "${response.body?.error?.code}" error message "${response.body?.error?.message}"

What it means

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).

Source

Thrown at cypress-tests/cypress/support/commands.js:8258

    const headers = {
      "Content-Type": contentType,
    };

    const sendRequest = () =>
      cy
        .request({
          method: "POST",
          url: completeUrl,
          headers,
          body,
          failOnStatusCode: false,
        })
        .then((response) => {
          logRequestId(response.headers["x-request-id"]);

          if (response.status !== 200) {
            throw new Error(
              `Webhook failed with error code "${response.body?.error?.code}" error message "${response.body?.error?.message}"`
            );
          }

          return cy.wrap(response);
        });

    // If signature required
    if (webhookConfig.webhookSecret) {
      const bodyString = JSON.stringify(webhookBody);
      body = bodyString;

      return cy
        .task("hmac_sha256", {
          secret: webhookConfig.webhookSecret,
          message: bodyString,
        })
        .then((signature) => {

View on GitHub (pinned to 9b8b89dc37)

Solutions

  1. 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.
  2. 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).
  3. If 404: verify completeUrl — correct base URL for the environment, correct path, webhook route enabled for the profile/tenant.
  4. If 400: inspect webhookBody against the endpoint's schema (required fields, types) and fix the payload in the test fixture.
  5. If 5xx/502/503: retry after confirming service health; this is a server-side failure, not a test bug.

Example fix

// before: signing one serialization, sending another
const signature = crypto.createHmac('sha256', secret).update(JSON.stringify(webhookBody)).digest('hex');
let body = webhookBody;

// after: sign the exact bytes that go on the wire
const bodyString = JSON.stringify(webhookBody);
const signature = crypto.createHmac('sha256', secret).update(bodyString).digest('hex');
const body = bodyString;
// headers carry the signature; request sends `body` unchanged
Defensive patterns

Strategy: validation

Validate before calling

// Run before invoking the webhook command
function validateWebhookCall(webhookConfig, webhookBody) {
  const problems = [];
  if (!webhookConfig?.url) problems.push('webhookConfig.url is empty — wrong env config');
  if (typeof webhookBody !== 'object' || webhookBody === null) problems.push('webhookBody must be an object');
  if (webhookConfig?.webhookSecret && typeof webhookConfig.webhookSecret !== 'string') {
    problems.push('webhookSecret present but not a string');
  }
  if (problems.length) throw new Error(`Webhook pre-flight failed: ${problems.join('; ')}`);
}
validateWebhookCall(webhookConfig, webhookBody);

Type guard

// Narrows the response body so you branch on a known envelope instead of guessing
function isApiErrorBody(body) {
  return (
    typeof body === 'object' &&
    body !== null &&
    'error' in body &&
    typeof body.error === 'object' &&
    body.error !== null &&
    typeof body.error.code === 'string' &&
    typeof body.error.message === 'string'
  );
}

Try / catch

// Cypress commands cannot be try/caught by the caller, so make the failure informative:
// branch on status with expect() instead of a blind throw inside the command's .then().then((response) => {
  if (response.status !== 200 && isApiErrorBody(response.body)) {
    Cypress.log({ name: 'webhook', message: `code=${response.body.error.code} msg=${response.body.error.message}` });
  }
  expect(response.status, 'webhook status').to.eq(200);
  return cy.wrap(response);
});
// If wrapping in a plain async helper instead of a cy command, the caller may use:
// try { await postWebhook(...) } catch (e) { if (/Webhook failed with error code/.test(e.message)) { /* inspect code, decide retry */ } else throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of juspay/hyperswitch@9b8b89dc37 (2026-08-16). Data as JSON: /api/errors/dc86c59fc2b12bcf. Report an issue: GitHub.