juspay/hyperswitch · error · Error

Customer create call failed with status: ${response.status}

Error message

Customer create call failed with status: ${response.status} and message: ${response.body?.error?.message}

What it means

Thrown by the customerCreateCall custom Cypress command after POSTing a customer-create request with failOnStatusCode-style handling: status 200 goes through field-echo assertions (e.g. phone_country_code), status 400 is tolerated only when the message contains 'already exists' (asserting code IR_12 and the exact message), and every other status falls into the else-branch throw with status and error.message. It means the Customer API responded with a status the command does not consider a known-good outcome.

Source

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

        expect(customerCreateBody.metadata, "metadata").to.deep.equal(
          response.body.metadata
        );
        expect(customerCreateBody.address, "address").to.deep.equal(
          response.body.address
        );
        expect(
          customerCreateBody.phone_country_code,
          "phone_country_code"
        ).to.equal(response.body.phone_country_code);
      } else if (response.status === 400) {
        if (response.body.error.message.includes("already exists")) {
          expect(response.body.error.code).to.equal("IR_12");
          expect(response.body.error.message).to.equal(
            "Customer with the given `customer_id` already exists"
          );
        }
      } else {
        throw new Error(
          `Customer create call failed with status: ${response.status} and message: ${response.body?.error?.message}`
        );
      }
    });
  }
);

// Payment Methods Commands (v2 code - port 8082)
Cypress.Commands.add("paymentMethodCreateCall", (globalState, pmData) => {
  const apiKey = globalState.get("apiKey");
  const profileId = globalState.get("profileId");
  const customerId = globalState.get("customerId");

  const requestBody = {
    ...pmData,
    customer_id: customerId,
  };

View on GitHub (pinned to 9b8b89dc37)

Solutions

  1. Parse the status and message out of the thrown text: 401 -> refresh/regenerate the API key in globalState; 403 -> check the key's access to the profile; 404 -> verify base URL and customers endpoint path.
  2. If 400: diff the actual message against 'Customer with the given `customer_id` already exists' — if it is a different validation error, fix the offending field (phone_country_code, email, customer_id format) in the customer fixture.
  3. Generate a unique customer_id per run (e.g. suffix with Date.now()/UUID) so the already-exists path only occurs when you intentionally test it.
  4. If the message text changed in a new server version, update the command's expected string/code (IR_12) to match the current contract.
  5. If 429/5xx: add retry with backoff or wait for service health before rerunning.

Example fix

// before: fixed customer_id, collisions fall into the throw on message drift
cy.customerCreateCall(globalState, { ...pmData, customer_id: 'cust_test_1' });

// after: unique id per run; 400 branch only reached deliberately
const customerId = `cust_${Cypress.moment().format('x')}_${Cypress.spec.name}`;
cy.customerCreateCall(globalState, { ...pmData, customer_id: customerId });
Defensive patterns

Strategy: validation

Validate before calling

// Run before customerCreateCall
function validateCustomerCreateCall(globalState, customerData) {
  if (!globalState.get('apiKey')) throw new Error('customerCreateCall: apiKey missing in globalState');
  if (!globalState.get('profileId')) throw new Error('customerCreateCall: profileId missing in globalState');
  if (!customerData?.customer_id) throw new Error('customerCreateCall: customer_id required');
  if (!/^[A-Za-z0-9_-]+$/.test(customerData.customer_id)) {
    throw new Error(`customerCreateCall: invalid customer_id '${customerData.customer_id}'`);
  }
}

Type guard

// Distinguish the tolerated 409-style 'already exists' body from other failures
function isAlreadyExistsError(body) {
  return (
    typeof body === 'object' &&
    body !== null &&
    body.error?.code === 'IR_12' &&
    typeof body.error?.message === 'string' &&
    body.error.message.includes('already exists')
  );
}

Try / catch

// Prefer result-object returns over throws so callers branch instead of catching:
//   const r = await customerCreate(state, data);
//   if (r.status === 400 && isAlreadyExistsError(r.body)) { /* reuse or regenerate id */ }
// For the existing throwing command, callers in plain Node wrappers can do:
// try { ... } catch (e) {
//   const m = e.message.match(/status: (\d+) and message: (.*)$/);
//   if (!m) throw e;
//   const [, status, msg] = m;
//   if (status === '400' && !msg.includes('already exists')) throw new Error(`payload invalid: ${msg}`);
//   if (status === '401') throw new Error('refresh API key');
// }

Prevention

When it happens

Trigger: 401 with a bad/expired API key (globalState 'apiKey'); 403 when the key cannot access the profile (globalState 'profileId'); 404 from a wrong base URL or endpoint path; 400 with any message other than 'Customer with the given `customer_id` already exists' (e.g. invalid phone_country_code, invalid email format); 429 rate limiting; 5xx server errors.

Common situations: Reusing a hardcoded customer_id across runs and hitting a 400 whose message differs slightly from the asserted string; API key expired or belonging to another environment; base URL/port pointing at the wrong service version; profile not provisioned; server contract change altering the already-exists message so the 400 branch no longer matches and 400 now falls through to the throw.

Related errors


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