Skip to Content
View as Markdown

오류

모든 4xx/5xx 응답은 동일한 JSON envelope를 가집니다.

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • code — 숫자 HTTP 상태(400, 401, 404, …). 일반 HTTP 레이어 처리에는 유용하지만, 분기 로직에는 대신 message로 분기하세요 — code는 예를 들어 invalid_input과 payment_method_not_supported(둘 다 400)를 구분하지 못합니다.
  • message — 소문자 스네이크 케이스로 표기한 센티넬 이름 (예: INVALID_INPUT은 "invalid_input"이 됩니다). 릴리스 간 안정적 입니다 — 이것으로 분기하세요.
  • details — 검증 오류 시 채워집니다. 클라이언트가 오류를 입력에 매핑할 수 있도록 { field, message } 객체 배열입니다. 그 외에는 생략됩니다.

오류 본문에는 trace ID나 타임스탬프가 포함되지 않습니다. 문제를 신고할 때는 응답 Date 헤더, X-RateLimit-* 헤더, 가맹점 ID, 엔드포인트, 대략적인 요청 시각을 지원팀에 보내세요.

인증 실패는 다른 형식을 사용합니다. 위의 envelope는 대부분의 오류에서 API가 반환하는 형식입니다. 처리되기 전에 거부된 요청(/b2b/v1/* 호출에서 누락/잘못된 X-Signature, 알 수 없는 X-Client-ID, 또는 오래된 X-Timestamp)은 { "error": "...", "message": "..." } 형태로 돌아옵니다. 여기서 error는 거친 슬러그(unauthorized / bad_request / service_unavailable)이고 message는 세부 사항을 전달합니다. 숫자 code도, details도 없습니다. 먼저 HTTP 상태로 분기한 다음 message를 읽으세요. 예시(401):

{ "error": "unauthorized", "message": "invalid signature" }

HTTP 상태 → 일반적인 코드

HTTP일반적인 code 값의미
400INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUM잘못된 요청 — details를 확인
401INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); SESSION_EXPIRED (대시보드만)인증 실패 — 잘못된 키, 만료된 타임스탬프, 잘못된 서명. 대시보드 로그인 코드는 B2B 통합과 무관합니다.
402INSUFFICIENT_CREDIT가맹점 선불 잔액 소진 — 재시도 전 충전 필요
403FORBIDDEN, IP_BLOCKED키는 유효하지만 이 호출에 대한 스코프/IP 허용 권한 없음
404NOT_FOUND, RECORD_NOT_FOUND리소스가 존재하지 않음(또는 가맹점에 대해 존재하지 않음)
409ALREADY_EXISTS다른 본문으로 멱등성 재시도, 또는 상태 머신이 전이를 거부함
429TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTS레이트 리밋 도달. 백오프 후 재시도
500INTERNAL_SERVER_ERROR, EXTERNAL_SERVICE_ERROR저희 측 문제. 백오프로 재시도 안전.
503SERVICE_UNAVAILABLE다운스트림 종속성 다운. 백오프 후 재시도

전체 코드 레퍼런스

볼 수 있는 code 값:

인증 (401)

  • INVALID_CREDENTIALS — API 키 조합 거부됨
  • INVALID_TOKEN — 토큰이 파싱 불가하거나 변조됨
  • TOKEN_EXPIRED — 토큰 만료
  • SESSION_EXPIRED — 대시보드 세션 만료
  • INVALID_SIGNATURE — B2B / 웹훅 호출의 HMAC 서명 불일치

권한 (403)

  • FORBIDDEN — 인증되었지만 이 작업을 수행할 권한이 없음
  • IP_BLOCKED — 이 IP 주소의 요청이 차단됨

찾을 수 없음 (404)

  • NOT_FOUND — 일반
  • RECORD_NOT_FOUND — 주어진 ID에 대한 행 누락

충돌 (409)

  • ALREADY_EXISTS — 일반

검증 (400)

  • INVALID_INPUT — 일반. details 확인
  • MISSING_REQUIRED — 필수 필드 누락
  • INVALID_FORMAT — 값이 예상 형식과 일치하지 않음(예: UUID, URL, email)
  • INVALID_LENGTH — 값이 너무 짧거나 너무 길음
  • INVALID_VALUE — 값이 허용된 enum/범위를 벗어남
  • PAYMENT_METHOD_NOT_SUPPORTED — 제공자/자산 조합이 가맹점에 활성화되지 않음
  • AMOUNT_BELOW_MINIMUM — 주문 금액이 네트워크별 또는 env 레벨 하한선 미만. 응답의 details.floor_usd에 구성된 하한선(USD)이 담겨 있으므로 바로 노출할 수 있습니다 — message 텍스트에도 동일한 내용이 표시됩니다.
  • INSUFFICIENT_BALANCE — 구매자 지갑이 전송 결제 자산을 충분히 보유하고 있지 않음.
  • INSUFFICIENT_GAS — 구매자 지갑에 전송을 브로드캐스트할 네이티브 토큰(네트워크 수수료용)이 부족함.

결제 / 청구 (402)

  • INSUFFICIENT_CREDIT — 가맹점 선불 잔액으로 네트워크 수수료(가스)와 플랫폼 수수료를 커버할 수 없음. 대시보드에서 충전 후 재시도

레이트 리밋 (429)

  • TOO_MANY_REQUESTS — IP 레이트 리밋 초과
  • TOO_MANY_ATTEMPTS — 동일 리소스에 반복된 실패 시도(예: OTP)가 스로틀 발동

서버 오류 (500)

  • EXTERNAL_SERVICE_ERROR — 서드파티 제공자 실패
  • INTERNAL_SERVER_ERROR — 예상치 못한 오류. 응답 Date 헤더와 X-RateLimit-* 헤더를 지원팀에 전달하여 문의

가용성 (503)

  • SERVICE_UNAVAILABLE — 서비스를 일시적으로 사용할 수 없음. 백오프 후 재시도

검증 오류 (400)

문제가 잘못된 요청 본문인 경우 details는 배열이므로 오류를 필드에 다시 매핑할 수 있습니다.

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

인증 오류 (401)

INVALID_SIGNATURE의 일반적인 원인은 세 가지입니다.

  • 잘못된 시크릿 키(env 변수 재확인)
  • 타임스탬프 드리프트 > 5분(NTP 동기화)
  • 잘못된 정규 문자열(가장 흔한 경우: \n 구분자 누락, 또는 보낸 것과 다른 파싱-후-재문자열화된 본문 서명)

정확한 서명 알고리즘은 인증을 참조하세요.

레이트 리밋 (429)

표면제한
모든 API 경로(/b2b/v1/* 포함)IP 주소당 — 500 req/min, 공유 버킷. 가맹점당이 아님.
공개 체크아웃(/checkout/*)더 엄격한 서브 버킷 — IP당 20 req/min

X-RateLimit-Limit과 X-RateLimit-Remaining은 레이트 리밋이 적용된 모든 응답(성공 응답뿐 아니라)에 포함됩니다. IP에 대한 실시간 예산으로 다루세요.

레이트 리밋 응답은 Retry-After 헤더(윈도우가 재설정될 때까지의 초)를 포함합니다 — 이를 준수하세요. 폴백으로 지터를 사용한 백오프 — 1s 베이스 + 30s까지 지수적 증가 — 를 적용하세요.

레이트 리밋은 변경될 수 있습니다. 정상적인 트래픽으로 한도에 도달하는 경우(예: 큰 과거 범위 리컨실리에이션), 지원팀에 문의하세요.

잔액 부족 (402)

INSUFFICIENT_CREDIT(HTTP 402 Payment Required)는 선불 잔액이 수행하려는 작업에 대한 네트워크 수수료(가스)와 플랫폼 수수료를 커버할 수 없음을 의미합니다. 일반적으로 암호화폐 결제 정산 또는 네트워크 수수료를 후원하는 온체인 작업에서 발생합니다. 가맹점 대시보드(Billing → Add credit)에서 충전한 후 작업을 재시도하세요. 이미 진행 중인 작업은 잔액이 충전되면 자동으로 이어집니다.

서버 오류 (5xx)

500은 요청을 처리할 수 없었음을 의미합니다. 백오프로 재시도하세요 — idempotency_key는 원래 요청이 부분적으로 성공했더라도 이중 청구를 방지합니다.

재시도가 1분 이내에 회복되지 않으면 구매자에게 일반적인 “결제 일시 사용 불가” 메시지를 노출하고 실패한 엔드포인트, 가맹점 ID, 응답 Date 헤더와 X-RateLimit-* 값, 대략적인 요청 시간을 지원팀에 문의하세요 — 이는 저희가 해당 요청을 찾기에 충분합니다.

웹훅 전달 오류

웹훅 전달은 별도의 실패 채널입니다 — 호출하는 것이 가맹점 서버가 아니므로 API 오류로 노출되지 않습니다. 전달이 non-2xx를 반환하거나 타임아웃되면, 0s, 1min, 5min, 15min, 1h, 6h(총 6회 시도 — 웹훅 → 개요와 동일한 스케줄)에서 지수 백오프로 재시도됩니다. 전달별 전체 이력은 대시보드의 Developers → Webhooks → [endpoint] → Delivery log 패널에 표시됩니다. 여섯 번째 시도가 실패하면 대시보드에서 해당 전달이 “Failed”로 표시되며, 서버가 정상화된 후 수동으로 재생할 수 있습니다.