EGG PAY

가맹점 API 설명서

가맹점 사이트에 EGG PAY 를 붙이면, 사용자가 결제한 금액만큼 USDE 가 가맹점 지갑으로 전송됩니다.

1. 시작하기

  1. 가맹점 신청을 하면 받을 지갑 주소의 SJO 지갑 앱으로 서명 요청이 갑니다. 지갑 앱에서 서명하시면 신청이 접수되고, 검토 후 알려 드립니다.
  2. 승인되면 가맹점 코드와 API 키를 드립니다.
  3. 사용자가 지갑 연동을 마쳐야 결제할 수 있습니다 (3절).
  4. 사용자가 거래소 회원이어야 결제할 수 있습니다. 연동 확인의 exchange.ready 로 보실 수 있습니다 (3절).

결제 순서

  1. 가맹점 사이트에서 충전 요청 — 가맹점 서버가 결제 요청을 부릅니다 (5절)
  2. 로그인 (회원이 아니면 회원가입) — 방식마다 다릅니다 (5절)
  3. 구매 등록
  4. 매칭
  5. 지갑 앱으로 이동
  6. 서명
  7. 컨트랙트 시작 — 끝나면 결과를 보내 드립니다 (6절)

가맹점이 하시는 곳은 결제 요청, API 만 방식의 구매 등록, 그리고 결과 받기(웹훅 · 상태 조회)입니다.

가맹점이 만드시는 것 — 충전 금액 입력창과 그 안의 환율·수수료 계산, 그리고 입금 내역을 확인하는 관리자 페이지입니다. 결제마다 방식(mode)도 골라 주십시오 (5절). API 만 방식(api)이면 사용자가 보는 화면도 가맹점이 만드십니다 — 「지갑 앱에서 승인하세요」 안내, [구매] 버튼, 매칭 대기와 [취소] 버튼, 「지갑 앱에서 서명하세요」 안내. 거래소 회원이 아닌 사용자에게는 거래소 가입을 안내해 주십시오.

2. 인증

환율(4절) 밖의 모든 요청에 두 헤더를 넣습니다.

헤더무엇
X-EggPay-Merchant-Id가맹점 코드
X-EggPay-Api-Key비밀 키
API 키를 브라우저에 내려보내지 마십시오. 가맹점 서버에서만 쓰십시오. 브라우저에 두면 누구나 그 키로 우리 API 를 부를 수 있습니다.

키는 둘입니다. API 키는 가맹점이 우리를 부를 때, 웹훅 비밀키는 우리가 가맹점을 부를 때 씁니다 (6절). API 키는 관리자 웹 「API」에서 [재발급], 웹훅 비밀키는 마이페이지의 「웹훅 서명 키」 [새로 발급]으로 가맹점이 직접 새로 받으실 수 있습니다 (9-1절).

둘 중 하나라도 없거나 틀리면 401 unauthorized 입니다 — 어느 쪽이 틀렸는지는 알려 드리지 않습니다. 승인된 가맹점이 아니거나 정지 · 삭제된 가맹점이면 403 merchant_inactive 입니다.

요청 공통

헤더언제무엇
X-EggPay-Merchant-Id · X-EggPay-Api-Key환율 밖 모두위의 두 헤더
Content-Type: application/json본문이 있는 요청 (연동 시작 · 결제 요청) 본문은 UTF-8 JSON 입니다. 다른 형식이면 400 invalid_request 입니다
Accept-Language골라서ko 또는 en — 오류의 message 언어입니다. 없으면 한국어입니다
Accept골라서보내실 때는 application/json 을 받게 해 주십시오. JSON 을 받지 않는다고 하시면 본문 없는 406 이 갑니다

다시 보내기 키는 따로 헤더로 받지 않습니다. 결제 요청 본문의 merchantRequestId 가 그 키입니다 (5절).

GET /api/v1/payments/pay_Q2x7Lr0kVb3sT9mWzA1cN-8e
X-EggPay-Merchant-Id: (가맹점 코드)
X-EggPay-Api-Key: (API 키)
Accept-Language: ko

호출 한도 (가맹점 하나당, 분당)

API분당세는 창구
결제 요청60결제 요청 · 구매 등록
결제 상태 조회600결제 상태 조회
지갑 연동60연동 시작 · 연동 확인 · 연동 해제
전체 합1,200모든 창구의 합. 지갑 잔액 조회와 매칭 전 취소는 이 몫만 셉니다

표의 값은 기본값입니다. 넘으면 429 와 함께 retryAfterSeconds 를 돌려드립니다. 한도는 1분 창(매 분 0초부터)으로 셉니다.

공통 모양

무엇모양
성공HTTP 200 과 JSON 본문입니다. 창구마다 아래에 본문 예를 적었습니다. 값이 없는 필드도 빠지지 않고 null 로 옵니다
오류{ "error": { "code": "…", "message": "…" } } 와 HTTP 상태. code 로 가르십시오. 429 에는 retryAfterSeconds 도 붙습니다 (10절)
금액소수 둘째 자리까지의 문자열입니다 (예 "12.50"). 셋째 자리가 오면 400 입니다 — 버리거나 반올림하지 않습니다
시각ISO-8601 UTC 문자열입니다 (예 2026-09-28T03:04:05.123Z). 웹훅 헤더 X-EggPay-Timestamp 만 초입니다 (6절)
모르는 필드보내시면 400 invalid_request 입니다
다시 보내기결제 요청에는 가맹점이 정하는 키 merchantRequestId 를 넣습니다. 답을 못 받으셨으면 같은 키, 같은 내용으로 다시 보내 주십시오 — 처음 결과를 드립니다. 같은 키에 다른 내용이면 409 conflict 입니다

오류를 받으면

창구마다 오류 표의 「하실 일」 칸은 아래 셋 가운데 하나입니다.

하실 일뜻
고쳐서 다시요청이 틀렸습니다. 그대로 다시 보내도 같은 오류입니다. 고친 뒤 보내 주십시오
잠시 뒤 다시잠깐 생긴 문제입니다. 같은 요청을 잠시 뒤 다시 보내 주십시오. 429 는 retryAfterSeconds 초가 지난 뒤 보내 주십시오
다시 보내지 마십시오다시 보내도 바뀌지 않습니다. 칸에 적힌 대로 먼저 다른 일을 해 주십시오

어느 창구에서나 오는 것은 이렇습니다.

3. 지갑 연동

사용자가 SJO 지갑앱을 설치하고, 계정 주소를 만들고, 가맹점 회원 아이디에 그 주소를 연동해 두어야 결제할 수 있습니다.

연동 시작

POST /api/v1/merchant/wallet-links

사용자의 SJO 지갑 주소를 가맹점 회원 아이디에 연동해 달라고 요청합니다. 한도는 「지갑 연동」 몫입니다.

요청

필드형식필수무엇예
merchantUserId문자열예가맹점 사이트의 회원 아이디 (1~64자). 연동 확인·해제에서 주소의 한 칸으로 쓰이므로 / · \ · ; 는 넣을 수 없고 . · .. 만으로 된 아이디도 안 됩니다 (400 invalid_request). 주소에 실을 때는 퍼센트 인코딩해 주십시오(예: 빈칸은 %20) "member-1024"
walletAddress문자열예사용자가 입력한 SJO 지갑 계정 주소 (sjo1…). 체크섬을 검사합니다 "sjo1…"
{ "merchantUserId": "member-1024", "walletAddress": "sjo1…" }

성공 응답 · 200 OK

{ "linkId": "lnk_Hk3Pq9Ws2LmX7vR0aTe5Yc_J", "status": "PENDING", "expiresAt": "2026-10-21T03:09:05.123Z" }
필드형식무엇
linkId문자열연동 요청 번호 (lnk_ 로 시작)
status문자열PENDING(사용자 승인 기다림) 또는 LINKED(이미 연동됨)
expiresAt시각이 요청의 마감 — 만든 때부터 5분

이 뒤로 사용자의 지갑앱에 알림이 가고, 사용자가 지갑앱에서 서명으로 승인하면 연동이 끝납니다. 옮겨 칠 숫자는 없습니다. 요청은 5분 동안 살아 있습니다 (expiresAt). 결과는 연동 확인으로 보십시오. 지갑 앱에 알림을 곧바로 전하지 못해도 답은 PENDING 이고, 마감 전까지 저희가 다시 보냅니다. 같은 회원·같은 주소로 다시 부르시면 살아 있는 요청을 그대로 드립니다. 같은 회원·같은 주소가 이미 연동돼 있으면 새 요청을 만들지 않고 그 연동을 "status": "LINKED" 로 드립니다. 이때 expiresAt 은 그 연동을 만든 요청의 마감(이미 지난 시각)입니다.

{ "linkId": "lnk_Hk3Pq9Ws2LmX7vR0aTe5Yc_J", "status": "LINKED", "expiresAt": "2026-10-20T01:05:00.000Z" }

한 가맹점 안에서 지갑 하나는 회원 하나에만 연동됩니다. 그 회원이 이미 다른 주소로, 또는 그 주소가 다른 회원으로 연동돼 있으면 409 wallet_link_conflict 입니다. 살아 있는 요청(5분 안)도 같습니다 — 그 회원의 다른 주소 요청, 또는 그 주소의 다른 회원 요청이 살아 있으면 409 wallet_link_conflict 입니다. 5분이 지난 요청은 막지 않습니다. 바꾸시려면 연동은 연동 해제 뒤, 요청은 expiresAt 이 지난 뒤 다시 시작해 주십시오. 그 주소의 지갑 사용자가 없으면 422 wallet_undeliverable 입니다.

오류

codeHTTP언제하실 일
invalid_request400본문이 JSON 이 아님 · 필드가 빠짐 · 모르는 필드 · 문자열이 아닌 값. merchantUserId 가 비었거나 64자를 넘거나 / · \ · ; 가 있거나 . · .. 뿐임 고쳐서 다시
invalid_address400walletAddress 가 SJO 계정 주소가 아님 (체크섬이 틀림 · 컨트랙트 주소 등) 사용자에게 주소를 다시 받아 고쳐서 다시
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
wallet_link_conflict409그 회원이 다른 주소로, 또는 그 주소가 다른 회원으로 연동됐거나 그런 요청이 살아 있음 (5분 안) 다시 보내지 마십시오 — 연동 해제 뒤, 또는 그 요청의 expiresAt 이 지난 뒤 다시 시작
wallet_undeliverable422그 주소를 쓰는 SJO 지갑 사용자가 없음. 그 요청은 거절(REJECTED)로 닫힙니다 다시 보내지 마십시오 — 사용자에게 주소와 지갑 앱을 확인받은 뒤 다시 시작
rate_limited429「지갑 연동」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시

연동 확인

GET /api/v1/merchant/wallet-links/{merchantUserId}

결제 버튼을 그리기 전에 부르십시오. 연동되고 거래소 준비가 된 사용자만 결제할 수 있습니다. 한도는 「지갑 연동」 몫입니다.

요청

자리이름형식필수무엇
주소merchantUserId문자열예연동 시작에 쓴 회원 아이디. 퍼센트 인코딩해서 넣습니다 (예: /api/v1/merchant/wallet-links/member-1024). 본문은 없습니다

성공 응답 · 200 OK

{ "linked": true, "status": "LINKED", "walletAddress": "sjo1…", "linkedAt": "2026-10-20T01:01:12.345Z",
  "exchange": { "ready": true, "reason": null } }
필드무엇
linkedstatus 가 LINKED 면 true
statusLINKED(연동됨) · PENDING(승인 기다림) · EXPIRED(5분 지남) · REJECTED(거절됨) · NONE(지금 연동 없음 — 연동한 적이 없거나 연동 해제함)
walletAddress · linkedAtLINKED 일 때만. 그 밖에는 null
exchangeLINKED 일 때만. ready: true 여야 그 주소로 결제를 받습니다. false 면 reason 에 거래소가 준 까닭(회원 아님 · 본인 확인 전 · 가입 승인 대기 · 정지 등)이 옵니다. 거래소에 닿지 못했으면 exchange 가 null 입니다 — 잠시 뒤 다시 조회해 주십시오

연동이 없는 회원도 오류가 아니라 이렇게 답합니다.

{ "linked": false, "status": "NONE", "walletAddress": null, "linkedAt": null, "exchange": null }

가맹점마다 따로 봅니다 — 다른 가맹점의 회원은 보이지 않습니다. 사용자가 지갑 앱에서 거절했거나 지갑 앱에 전할 수 없었으면 REJECTED, 5분 안에 승인이 없었거나 지갑 서명을 확인하지 못했으면 EXPIRED 입니다. 이름 · 거래소 아이디 같은 개인정보는 싣지 않습니다.

오류

codeHTTP언제하실 일
invalid_request400회원 아이디가 64자를 넘거나 . · .. 뿐이거나, 주소에 인코딩하지 않은 ; 가 있음 (그 뒤가 잘려 다른 아이디가 되므로 읽지 않습니다)고쳐서 다시
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
rate_limited429「지갑 연동」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시

연동 해제

DELETE /api/v1/merchant/wallet-links/{merchantUserId}

회원의 연동을 풉니다. 한도는 「지갑 연동」 몫입니다.

요청

자리이름형식필수무엇
주소merchantUserId문자열예연동 확인과 같습니다 (퍼센트 인코딩). 본문은 없습니다

성공 응답 · 200 OK

{ "linked": false }

이 가맹점에서 그 지갑의 팝업 결제가 아직 요청 기록(REQUESTED)이면 먼저 닫고 풉니다 — 그 결제는 거래취소 (request_closed)가 되고 웹훅이 갑니다. 연동 기록은 지우지 않지만, 해제한 뒤 연동 확인은 NONE 입니다. 새 주소는 연동 시작부터 다시 해 주십시오.

그 지갑으로 진행 중인 결제가 있으면 409 payment_in_flight 입니다. 연동된 것이 없으면 404 입니다.

오류

codeHTTP언제하실 일
invalid_request400회원 아이디가 64자를 넘거나 . · .. 뿐이거나, 주소에 인코딩하지 않은 ; 가 있음 — 풀지 않았습니다고쳐서 다시
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
not_found404이 가맹점에 그 회원의 연동이 없음 (이미 해제한 뒤 다시 불러도 이것입니다) 다시 보내지 마십시오 — 이미 풀려 있습니다
payment_in_flight409그 지갑으로 이 가맹점에 진행 중인 결제(TRADING · DISPUTED)가 있음. 팝업 결제가 이미 거래소 주문을 가졌을 때도 이것입니다그 결제가 끝난 뒤(웹훅 · 결제 상태 조회) 다시
buyer_busy409같은 지갑의 다른 요청(결제 요청 · 연동 해제)을 지금 처리하고 있음잠시 뒤 같은 요청을 다시
rate_limited429「지갑 연동」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시
exchange_unavailable503요청 기록을 닫으려 했는데 거래소가 답하지 않음 — 풀지 않았습니다잠시 뒤 다시

4. 환율

GET /api/v1/rates

키가 필요 없습니다. 사용자가 금액을 치는 동안 브라우저에서 바로 불러 환산해 보여주실 수 있습니다 (다른 사이트에서 불러도 됩니다). IP 하나당 분당 120회입니다 (기본값).

요청

헤더 · 주소 값 · 본문 모두 없습니다.

성공 응답 · 200 OK

{ "base": "USDE", "rates": { "USD": 1.0, "KRW": 1342.34042 }, "timestamp": 1791528601, "next_update": 1791550201 }
필드형식무엇
base문자열늘 "USDE"
rates객체통화 코드 → 1 USDE 가 그 통화로 얼마인지 (숫자). 통화와 자릿수는 재단이 준 그대로입니다
timestamp숫자재단이 정한 기준 시각 (유닉스 초)
next_update숫자재단의 다음 갱신 시각 (유닉스 초)
이 창구는 재단 환율을 그대로 드립니다. 1 USDE 가 각 통화로 얼마인지가 rates 에 있습니다. 가맹점은 이 환율을 쓰셔야 합니다.

환율은 매일 한국 시간 0시와 12시에 재단에서 받아 바뀝니다. timestamp 와 next_update 는 재단이 정한 기준 시각과 재단의 다음 갱신 시각입니다(유닉스 초). 그 시각에 재단 환율을 받지 못하면 앞서 받은 환율을 그대로 드립니다.

받아 둔 환율이 하나도 없을 때만 503 과 rate_unavailable 을 드립니다 — 잠시 후 다시 불러 주십시오.

{ "error": { "code": "rate_unavailable", "message": "…" } }

오류

codeHTTP언제하실 일
rate_unavailable503저장된 환율이 없음 — 받아 둔 환율이 하나도 없을 때만입니다잠시 뒤 다시
rate_limited429IP 하나당 분당 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시

5. 결제

방식 셋

결제를 요청할 때 mode 로 고르십니다. 로그인과 구매 등록만 다르고, 매칭부터는 같습니다.

mode화면로그인구매 등록
apiAPI 만 — 가맹점 화면사용자가 지갑 앱에서 승인 가맹점 서버가 구매 등록을 부릅니다
popup_wallet팝업사용자가 지갑 앱에서 승인 사용자가 팝업에서
popup_password팝업거래소 아이디 · 비밀번호 + OTP 사용자가 팝업에서

결제 요청

POST /api/v1/payments

한도는 「결제 요청」 몫입니다.

요청

필드형식필수무엇예
merchantRequestId문자열예가맹점이 정하는 다시 보내기 키. 가맹점마다 유일. 1~64자, 영문 · 숫자 · _ - . :"order-20261021-0001"
buyerWalletAddress문자열예사용자의 지갑 주소 — 이 가맹점에 연동돼 있어야 합니다"sjo1…"
merchantWalletAddress문자열예가맹점의 지갑 주소 — 등록하신 지급 주소와 같아야 합니다"sjo1…"
amountUsde문자열예구매할 USDE 수량, 소수 둘째 자리까지 (예 "12.50"). 0 보다 커야 하고 정수부는 16자리까지입니다. 숫자(12.5)로 보내시면 400 입니다"100.00"
mode문자열예api · popup_wallet · popup_password"popup_wallet"
{ "merchantRequestId": "order-20261021-0001", "buyerWalletAddress": "sjo1…", "merchantWalletAddress": "sjo1…",
  "amountUsde": "100.00", "mode": "popup_wallet" }

성공 응답 · 200 OK

{ "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e", "status": "REQUESTED", "statusReason": null,
  "popupUrl": "https://…", "created": true }

api 방식이면 이렇습니다.

{ "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e", "status": "TRADING", "statusReason": null,
  "popupUrl": null, "created": true }
필드무엇
paymentId결제 번호 (pay_ 로 시작). 구매 등록 · 취소 · 조회 · 웹훅에서 이 번호를 씁니다
statusapi 는 TRADING — 거래소 회원이 아니면 곧바로 CANCELLED 입니다. 팝업 둘은 REQUESTED(요청 기록)
statusReasonCANCELLED 일 때 까닭 (7절)
popupUrl팝업 둘만. 아직 준비되지 않았으면 null — 결제 상태 조회로 다시 읽어 주십시오. api 는 늘 null
created이 호출로 처음 만들었으면 true, 같은 merchantRequestId 의 처음 결과면 false

필드가 하나라도 없거나 잘못되면 거절합니다. 그때는 가맹점 화면에서 경고를 띄워 주십시오. 저희는 아래 차례로 봅니다 — 형식 → 같은 merchantRequestId → 가맹점 지갑 → 연동 → USDE 멈춤 → 진행 중 결제 → 거래소 회원 자격(api 만).

오류

codeHTTP언제하실 일
invalid_request400본문이 JSON 이 아님 · 필드가 빠짐 · 모르는 필드 · 형식이 틀림 (merchantRequestId 글자 · mode 이름 · 수량이 문자열이 아니거나 소수 셋째 자리 · 0 이하)고쳐서 다시
invalid_address400구매자 지갑이 SJO 계정 주소가 아니거나 가맹점 지갑이 SJO 주소가 아님 (체크섬)고쳐서 다시
merchant_wallet_mismatch400merchantWalletAddress 가 등록하신 지급 주소와 다름 등록된 지급 주소로 고쳐서 다시
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점 (결제를 만드는 순간에 정지된 경우도 — 결제를 만들지 않았습니다)다시 보내지 마십시오 — 고객센터에 문의해 주십시오
wallet_not_linked404구매자 지갑이 이 가맹점에 연동되지 않음다시 보내지 마십시오 — 먼저 지갑 연동 (3절)
conflict409같은 merchantRequestId 로 다른 내용이 이미 왔음 다시 보내지 마십시오 — 새 결제라면 새 merchantRequestId 로
buyer_busy409같은 구매자 지갑의 다른 요청(결제 요청 · 연동 해제)을 지금 처리하고 있음 잠시 뒤 같은 요청을 그대로 다시
payment_in_flight409그 지갑에 진행 중인 결제가 있음 (지갑당 1건 — 다른 가맹점의 결제도 셉니다). 앞의 팝업 결제가 이미 거래소 주문을 가졌을 때도 이것입니다 그 결제가 끝난 뒤(웹훅 · 결제 상태 조회) 다시
rate_limited429「결제 요청」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 같은 merchantRequestId 로 다시
usde_paused503USDE 가 멈춰 있음 — 결제를 만들지 않았습니다잠시 뒤 다시 (멈춤이 풀려야 받습니다)
chain_unavailable503USDE 멈춤 상태를 체인에서 읽지 못함 — 결제를 만들지 않았습니다 잠시 뒤 같은 merchantRequestId 로 다시
exchange_unavailable503거래소가 답하지 않음 (앞 요청 기록 닫기 · api 의 회원 자격 확인) — 결제를 만들지 않았습니다 잠시 뒤 같은 merchantRequestId 로 다시

팝업 열기

popup_wallet · popup_password 만 해당합니다.

window.open(popupUrl, '_blank', 'noopener,noreferrer')

새 창으로 여십시오. 틀(iframe) 안에 넣지 마십시오 — 팝업은 거래소 화면입니다. PC 와 모바일 둘 다 됩니다.

사용자가결제는
구매 등록 전에 팝업을 닫음요청 기록 그대로입니다. 같은 popupUrl 로 다시 여시거나 매칭 전 취소로 닫으십시오
구매 화면에서 [Cancel]거래취소 (request_closed)
구매 등록 뒤, 매칭 전에 진행 화면을 닫음거래취소 (popup_closed)
popupUrl 은 받은 그대로 여십시오. 수량은 저희가 들고 있는 값을 씁니다 — 브라우저가 금액을 들고 있으면 사용자가 고칠 수 있습니다.

구매 등록

api 만 해당합니다. 한도는 「결제 요청」 몫입니다.

POST /api/v1/payments/{paymentId}/purchase

요청

자리이름형식필수무엇
주소paymentId문자열예결제 요청이 준 결제 번호. 본문은 없습니다

사용자가 지갑 앱에서 로그인을 승인한 뒤 5분 안에 가맹점 화면의 [구매] 를 누르면 부르십시오. 409 login_required 면 아직 승인 전입니다 — 잠시 뒤 다시 부르십시오. 승인 뒤 5분 안에 [구매] 가 없으면 결제는 거래취소 (login_expired)입니다.

성공 응답 · 200 OK

{ "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e", "status": "TRADING", "statusReason": null, "orderStatus": "WAITING" }
필드무엇
paymentId결제 번호
statusTRADING, 거래소가 주문을 거절했으면 CANCELLED
statusReasonCANCELLED 일 때 거래소가 준 까닭 (7절). 그 밖에는 null
orderStatus거래소 주문 상태 (예 WAITING). 아직 모르면 null

거래소가 주문을 거절하면 status 가 CANCELLED 이고 까닭이 statusReason 에 옵니다 — 오류가 아니라 200 입니다. 한 번 받아들인 구매 등록은 다시 부르셔도 다른 확인 없이 그 결제의 지금 값을 드립니다. 방식이 api 가 아니면 409 wrong_mode, 결제가 거래중이 아니거나 취소 요청이 있으면 409 payment_not_open 입니다.

오류

codeHTTP언제하실 일
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
not_found404없는 결제이거나 다른 가맹점의 결제다시 보내지 마십시오 — paymentId 를 확인하십시오
wrong_mode409방식이 api 가 아님 (팝업 방식은 사용자가 팝업에서 구매 등록)다시 보내지 마십시오
payment_not_open409결제가 거래중(TRADING)이 아니거나 매칭 전 취소를 받은 뒤임 다시 보내지 마십시오 — 결제 상태 조회로 지금 상태를 보십시오
login_required409지갑 앱 로그인 승인이 아직 없거나 승인 뒤 5분이 지남 잠시 뒤 다시. 5분이 지났으면 결제는 거래취소(login_expired)가 됩니다
rate_limited429「결제 요청」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시
exchange_unavailable503거래소가 답하지 않음. 구매 등록은 받아 두었고 저희가 거래소에 다시 보냅니다 — 결제는 거래중 그대로 잠시 뒤 다시 부르시거나 웹훅 · 결제 상태 조회로 결과를 보십시오

매칭 전 취소

POST /api/v1/payments/{paymentId}/cancel

매칭 전에만 됩니다. 매칭 뒤면 409 already_matched — 결제는 그대로 진행됩니다. 이미 거래완료면 409 payment_finished 입니다. 한도는 「전체 합」 몫만 셉니다.

요청

자리이름형식필수무엇
주소paymentId문자열예결제 번호. 본문은 없습니다

성공 응답 · 200 OK

{ "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e", "status": "CANCELLED", "statusReason": "cancelled_before_match" }
필드무엇
paymentId결제 번호
statusCANCELLED
statusReason보통 cancelled_before_match. 그 사이 거래소에서 먼저 끝났으면 그 까닭 (request_closed · popup_closed 등, 7절)

이미 거래취소된 결제면 같은 답을 다시 드립니다. 거래취소도 웹훅이 갑니다. 취소를 받은 뒤에는 구매 등록이 409 payment_not_open 입니다.

거래소나 지갑이 곧바로 답하지 않으면 503 입니다. 이때 취소 요청은 받아 두었고 저희가 다시 보냅니다 — 거래취소가 되면 웹훅으로 알려 드립니다. 그 사이 매칭되면 결제는 그대로 진행됩니다.

오류

codeHTTP언제하실 일
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
not_found404없는 결제이거나 다른 가맹점의 결제다시 보내지 마십시오 — paymentId 를 확인하십시오
already_matched409이미 매칭됐거나 분쟁 중 — 취소하지 않았고 결제는 그대로 진행됩니다 다시 보내지 마십시오 — 결과는 웹훅 · 결제 상태 조회로
payment_finished409이미 거래완료다시 보내지 마십시오
rate_limited429「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시
wallet_unavailable503지갑이 답하지 않음 — 취소 요청은 받아 두었고 저희가 다시 보냅니다 웹훅을 기다리시거나 잠시 뒤 다시
exchange_unavailable503거래소가 답하지 않음 — 취소 요청은 받아 두었고 저희가 다시 보냅니다 웹훅을 기다리시거나 잠시 뒤 다시

결제 상태 조회

GET /api/v1/payments/{paymentId}

웹훅을 못 받으셨을 때 따라잡는 창구입니다. 한도는 「결제 상태 조회」 몫입니다.

요청

자리이름형식필수무엇
주소paymentId문자열예결제 번호. 본문은 없습니다

성공 응답 · 200 OK

{ "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e", "mode": "popup_wallet", "popupUrl": "https://…",
  "status": "COMPLETED", "statusReason": null, "merchantUserId": "member-1024",
  "buyerWalletAddress": "sjo1…", "merchantWalletAddress": "sjo1…", "amountUsde": "100.00",
  "tradeNo": "…", "contract": { "address": "sjo1…", "paymentId": "pay_Q2x7Lr0kVb3sT9mWzA1cN-8e",
  "status": "paid", "merchantAmountUsde": "99.73" }, "arrivedUsde": "99.73", "txHash": "…",
  "createdAt": "2026-10-21T03:04:05.123Z", "updatedAt": "2026-10-21T03:12:40.551Z" }
필드무엇
paymentId결제 번호
modeapi · popup_wallet · popup_password
popupUrl팝업 둘의 주소. REQUESTED 면 다시 열 수 있습니다. api 는 null
status7절의 다섯 중 하나
statusReasonCANCELLED 일 때 까닭 (7절)
merchantUserId연동된 가맹점 회원 아이디
buyerWalletAddress구매자 지갑
merchantWalletAddress가맹점 지갑
amountUsde요청 수량 (예 "100.00")
tradeNo거래소 거래번호. 매칭 전에는 null. 한 번 받으면 바뀌지 않습니다
contract결제 컨트랙트 — { address, paymentId, status, merchantAmountUsde }. status 는 registered · paid · cancelled. 등록 전에는 null
arrivedUsde가맹점 지갑에 도착한 수량 (예 "99.73"). 확인 전에는 null
txHash블록체인 기록. 확인 전에는 null
createdAt · updatedAt만든 때 · 마지막으로 바뀐 때

오류

codeHTTP언제하실 일
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
not_found404없는 결제이거나 다른 가맹점의 결제다시 보내지 마십시오 — paymentId 를 확인하십시오
rate_limited429「결제 상태 조회」 또는 「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시

지갑 잔액 조회

GET /api/v1/merchant/wallets/{walletAddress}/balance

한도는 「전체 합」 몫만 셉니다.

요청

자리이름형식필수무엇
주소walletAddress문자열예잔액을 볼 SJO 주소 (sjo1…). 본문은 없습니다

성공 응답 · 200 OK

{ "walletAddress": "sjo1…", "balanceUsde": "12.50", "height": 1234567 }
필드형식무엇
walletAddress문자열보내신 주소
balanceUsde문자열USDE 잔액, 소수 둘째 자리
height숫자잔액을 읽은 블록 높이

그때 체인에서 읽은 USDE 잔액과 블록 높이입니다. 한 번 더 확인하는 용도입니다 — 어느 결제의 입금인지는 가르지 못합니다. 결제의 입금은 웹훅 · 상태 조회의 arrivedUsde · txHash 로 보십시오. 체인을 읽지 못하면 503 chain_unavailable 입니다.

오류

codeHTTP언제하실 일
invalid_address400SJO 주소가 아님 (체크섬)고쳐서 다시
unauthorized401헤더가 없거나 가맹점 코드 · API 키가 틀림다시 보내지 마십시오 — 키를 확인하십시오
merchant_inactive403승인된 가맹점이 아니거나 정지 · 삭제된 가맹점다시 보내지 마십시오 — 고객센터에 문의해 주십시오
rate_limited429「전체 합」 한도를 넘음잠시 뒤 다시 (retryAfterSeconds 뒤)
internal500저희 쪽 문제잠시 뒤 다시
chain_unavailable503체인에서 잔액을 읽지 못함잠시 뒤 다시

6. 웹훅 — 저희가 보내드리는 것

결제 상태가 바뀌면 저희가 등록해 주신 주소(마이페이지의 웹훅 주소, https://)로 POST 합니다. 웹훅 주소가 비어 있으면 보내지 않습니다 — 그때는 결제 상태 조회로 보십시오. 사설망 같은 내부 주소로 가는 웹훅 주소에는 보내지 않습니다.

언제

상태가 바뀔 때마다 한 번 보냅니다 — 요청 기록에서 거래중(REQUESTED → TRADING), 거래완료(COMPLETED), 거래취소(CANCELLED), 분쟁(DISPUTED). 결제를 만들 때는 보내지 않습니다 — 요청 기록(REQUESTED), api 방식의 TRADING, 곧바로 거래취소(CANCELLED) 모두 결제 요청의 답으로 아십니다. 만든 뒤에 바뀐 것은 보냅니다 — 결제 요청 처리 중 뒤이어 거래취소가 된 경우(wallet_undeliverable 등)도 웹훅이 갑니다.

저희가 보내는 요청

헤더무엇
Content-Typeapplication/json (UTF-8)
X-EggPay-Signature보낸시각 + "." + 본문 을 웹훅 비밀키로 HMAC-SHA256 한 값 — 소문자 16진수 64자
X-EggPay-Timestamp보낸 시각 — 1970년부터 흐른 초 (예: 1789545372)
{"paymentId":"pay_Q2x7Lr0kVb3sT9mWzA1cN-8e","status":"COMPLETED","statusReason":null,"amountUsde":"100.00","tradeNo":"…","contract":{"address":"sjo1…","paymentId":"pay_Q2x7Lr0kVb3sT9mWzA1cN-8e","status":"paid","merchantAmountUsde":"99.73"},"arrivedUsde":"99.73","txHash":"…","occurredAt":"2026-10-21T03:12:40.551Z"}

본문의 필드는 결제 상태 조회와 같은 뜻입니다. 모든 필드가 늘 오고, 값이 없으면 null 입니다.

필드무엇
paymentId결제 번호
status바뀐 뒤 상태 — TRADING · COMPLETED · CANCELLED · DISPUTED
statusReason거래취소 까닭 (7절). incident 면 충전을 취소하라는 신호입니다
amountUsde요청 수량
tradeNo거래소 거래번호
contract{ address, paymentId, status, merchantAmountUsde }
arrivedUsde거래완료면 가맹점 지갑에 도착한 수량
txHash거래완료면 블록체인 기록
occurredAt상태가 바뀐 때

상태마다 하실 일은 7절에 있습니다.

서명 확인

signature = HMAC-SHA256(웹훅 비밀키, X-EggPay-Timestamp + "." + 받은 본문)
  1. 본문을 받은 바이트 그대로 둡니다 (JSON 으로 읽기 전).
  2. X-EggPay-Timestamp 값, 점(.) 하나, 받은 본문을 이어 붙입니다.
  3. 웹훅 비밀키 글자(UTF-8)를 키로 HMAC-SHA256 을 계산해 소문자 16진수로 씁니다.
  4. X-EggPay-Signature 와 같은지 봅니다. 시간 차가 드러나지 않는 비교를 쓰십시오. 다르면 버리십시오.

⚠ 받은 본문 그대로 서명해 주십시오. JSON 을 다시 만들면 띄어쓰기 하나로 서명이 달라집니다. 시각이 서명 안에 들어 있어, 한 번 가로챈 알림을 나중에 다시 보내도 가맹점이 걸러낼 수 있습니다.

계산이 맞는지 볼 때 쓰는 예입니다 (실제 본문이 아닙니다).

무엇값
웹훅 비밀키test_secret
X-EggPay-Timestamp1789545372
본문{"paymentId":"pay_test"}
X-EggPay-Signature09b3c7c727cf96bfd81efd65741ffc2cdf0ba4474b355e63df6c406d5b160bf5
// Java
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(webhookSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((timestampHeader + ".").getBytes(StandardCharsets.UTF_8));
String expected = HexFormat.of().formatHex(mac.doFinal(rawBody));   // rawBody: 받은 본문 byte[]
boolean ok = MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8),
                                   signatureHeader.getBytes(StandardCharsets.UTF_8));
서명을 반드시 검증하십시오. 검증하지 않으면 남이 가짜 「거래완료」를 보내 받지도 않은 코인에 충전을 해주게 됩니다.

가맹점의 답

2xx 를 돌려주시면 됩니다. 3xx 는 따르지 않고 실패로 봅니다. 그 밖이면 6회까지 다시 보냅니다 — 30초 · 2분 · 8분 · 32분 · 2시간 · 8시간 뒤입니다 (기본값).

가맹점의 답저희는
2xx전달됨으로 적고 끝냅니다. 답의 본문은 보지 않습니다
3xx따라가지 않고 실패로 봅니다
4xx · 5xx실패로 보고 다시 보냅니다
10초(기본값) 안에 답이 없음 · 연결 안 됨실패로 보고 다시 보냅니다

다시 보내기 간격은 앞 실패에서 잽니다. 다시 보낼 때도 본문은 처음과 같고, X-EggPay-Timestamp 와 서명만 새로 만듭니다. 처음 1회와 다시 보내기 6회가 모두 실패하면 그 알림은 더 보내지 않습니다 — 결제 상태 조회로 보십시오. 답이 늦어 실패로 본 뒤 다시 보내면 같은 알림을 두 번 받으실 수 있습니다 — paymentId 와 status 로 한 번만 처리해 주십시오.

순서는 보장하지 않습니다. occurredAt 과 상태 조회로 최신을 가려 주십시오.

웹훅은 못 갈 수 있습니다. 서버가 잠깐 죽거나 네트워크가 끊기면 그 줄기가 끊깁니다. 상태 조회로 따라잡아 주십시오 — 그래서 둘 다 드리는 것입니다.

7. 결제 상태

값뜻가맹점이 하실 일
REQUESTED요청 기록 — 팝업 방식에서 구매 등록 전입니다. 진행 중이 아닙니다 기다립니다. 같은 popupUrl 로 다시 열 수 있습니다. 원하시면 매칭 전 취소로 닫으십시오
TRADING거래중기다립니다
COMPLETED거래완료 arrivedUsde 를 확인하고 충전해 주십시오. 원하시면 지갑 잔액 조회로 한 번 더 보십시오
CANCELLED거래취소충전건을 취소 처리해 주십시오. 까닭은 statusReason
DISPUTED분쟁결말이 날 때까지 기다립니다

결제는 거래완료나 거래취소 가운데 하나로 반드시 끝납니다.

거래취소 까닭 (statusReason)

값언제
popup_closed팝업 방식 — 구매 등록 뒤 매칭 전에 진행 화면을 닫음
cancelled_before_match매칭 전 취소 (가맹점 취소 · 진행 화면 [취소] · 거래소 앱)
request_closed팝업 방식의 요청 기록이 닫힘 (같은 구매자의 새 충전 · 연동 해제 · 구매 화면 [Cancel])
login_rejected · login_expiredapi 방식 — 지갑 앱 로그인 거절 · 5분 지남 (로그인 뒤 [구매] 없이 지남 포함)
not_member · account_withdrawn · account_inactive · kyc_required · grade_not_allowed 거래소 회원 자격이 없음 (거래소가 준 이름)
amount_out_of_range · payment_closed · exchange_halted · account_locked · wallet_suspended · login_invalid · member_busy거래소가 주문을 거절함 (거래소가 준 이름)
wallet_undeliverable지갑 앱에 전할 수 없음
mismatch결제 번호 · 수량 · 주소가 맞지 않아 진행하지 않음
trade_cancelled거래가 취소됨 (서명 마감 등)
incident거래소 사고처리 — 충전 취소 신호
registration_mismatch결제 컨트랙트 등록이 맞지 않음
pay_failed가맹점 송금이 체인에서 확정되지 못함
merchant_suspended가맹점이 정지됨 — 그때 매칭 전이던 결제. 이미 매칭된 결제는 끝까지 갑니다. 정지된 동안에는 로그인과 API 가 모두 막히고(403 merchant_inactive), 정지된 동안에도 웹훅은 보내 드립니다

거래소가 준 까닭은 그 이름 그대로 싣습니다. 위 표에 없는 이름이 오더라도 CANCELLED 면 충전건을 취소 처리해 주십시오.

8. 수수료

간편결제는 수수료를 받지 않습니다. 도착 수량에서 체인 전송 수수료와 가스만 빠집니다 — 100.00 USDE 미만은 전송 수수료 0.17 + 가스 0.02 USDE 고정, 100.00 USDE 부터는 전송 수수료 0.17% 와 가스 0.1% 를 각각 올림(0.01 USDE 단위)합니다.

결제 수량 amountUsde전송 수수료가스도착 arrivedUsde
"12.50"0.170.02"12.31"
"50.00"0.170.02"49.81"
"99.99"0.170.02"99.80"
"100.00"0.170.10"99.73"
"100.01"0.180.11"99.72"
"1000.00"1.701.00"997.30"
"1234.56"2.101.24"1231.22"

도착 수량은 체인이 정한 값(arrivedUsde)이 기준입니다. 식으로 다시 계산해 맞추지 않으셔도 됩니다.

100개 결제의 예

가맹점 지갑에 도착99.73 USDE
가맹점이 충전해 주실 것100개에 대한 머니

9. 관리자 웹

승인되면 가맹점 계정도 함께 드립니다. 관리자 웹에서 가맹점 계정으로 하실 수 있는 일은 이렇습니다.

무엇내용
거래내역누가(가맹점 회원 아이디 merchantUserId) · 어디서(출발 지갑) · 어디로(도착 지갑) · 몇 개 입금했는지 · 상태(요청 기록 · 거래중 · 거래완료 · 거래취소 · 분쟁) · 알림(전달됨 · 보내는 중 · 전달 실패)
결제 찾기손님 문의가 들어왔을 때 지갑 주소나 회원 아이디(둘 다 정확히 일치) 와 기간으로 찾으실 수 있습니다. 찾으시면 결제 건수 · 등록 합계 · 도착 합계 · 마지막 결제가 함께 보입니다
마이페이지내 가맹점 정보(이름·받을 지갑 주소·웹훅 주소·전화·연락 방법 — 맨 아래 [저장] 하나) · 웹훅 서명 키 [새로 발급] · 지급 지갑 잔액 · 비밀번호 바꾸기
APIAPI 키 목록(발급한 때·상태) · [재발급] · 새 API 키 받기
화면의 시각과 기간은 UTC 기준입니다 — 한국 시간과 9시간 차이가 납니다. 목록은 한 페이지에 50건씩 보이고, 지갑 주소는 줄여 보이다가 마우스를 올리면 전체가 뜹니다.
처음 드리는 비밀번호는 임시 비밀번호입니다. 첫 로그인 때 반드시 바꾸셔야 다른 화면으로 가실 수 있습니다.

9-1. 가맹점 정보를 바꾸려면

가맹점 이름·받을 지갑 주소·웹훅 주소·전화·연락 방법은 마이페이지에서 직접 바꾸실 수 있습니다. 「내 가맹점 정보」에서 고치고 맨 아래 [저장]을 누르시면 됩니다. 신청과 승인은 없습니다. 틀린 칸이 있으면 그 칸 아래에 까닭이 뜨고 아무것도 저장되지 않습니다.

바꾸는 것어떻게
가맹점 이름 · 전화 · 연락 방법마이페이지에서 고치고 [저장] — 곧바로 바뀝니다
받을 지갑 주소마이페이지에서 새 주소를 넣고 [저장] → 그 주소의 SJO 지갑 앱에서 [서명] — 서명하면 곧바로 바뀝니다. 취소하시거나 5분이 지나면 바뀌지 않습니다. 그 주소를 쓰는 SJO 지갑이 없으면 등록되지 않습니다. 이미 진행 중인 결제는 요청 때의 주소로 끝납니다
웹훅 받을 주소마이페이지에서 고치고 [저장] — 곧바로 바뀝니다. https:// 로 시작해야 하고, 비우시면 웹훅을 보내지 않습니다
웹훅 비밀키마이페이지 웹훅 주소 아래 「웹훅 서명 키」 [새로 발급] — [저장]과 따로 곧바로 됩니다. 새 키는 그 화면에 한 번만 보입니다. 지금 키는 곧바로 못 쓰고, 그 뒤 웹훅은 모두 새 키로 서명합니다
API 키 재발급관리자 웹 「API」에서 [재발급] — 신청 없이 곧바로 새 키가 나오고 그 화면에 한 번만 보입니다. 그 키는 곧바로 못 씁니다. 저희가 발급해 드린 새 키가 있으면 같은 화면에서 [새 키 받기]로 한 번 받아 가실 수 있습니다
가맹점 탈퇴고객센터로 요청해 주십시오. 저희가 확인하고 처리합니다. 처리되면 그때부터 로그인과 API 가 막히고 목록에서 사라집니다. 이미 들어온 결제 내역은 남습니다
받을 지갑 주소는 왜 서명이 필요한가요? 돈을 받는 주소는 한 번 바뀌면 그 뒤 결제가 전부 그리로 갑니다. 그 주소의 SJO 지갑 앱에서 서명해야만 바뀝니다.

10. 오류

{ "error": { "code": "invalid_address", "message": "…" } }

message 는 사람이 읽는 글입니다(Accept-Language 에 따라 한국어 · 영어). 프로그램은 code 로 가르십시오. 429 에는 error 옆에 retryAfterSeconds(다시 보내도 되는 때까지의 초)가 붙고, 같은 값이 Retry-After 헤더에도 옵니다.

{ "error": { "code": "rate_limited", "message": "…" }, "retryAfterSeconds": 12 }

아래는 가맹점 API 가 돌려드리는 모든 오류 코드입니다. 창구마다 언제 오는지는 각 창구의 오류 표에 있습니다.

codeHTTP창구뜻하실 일
invalid_request400연동 시작 · 연동 확인 · 연동 해제 · 결제 요청형식 · 필수 칸 · 길이 · 모르는 필드 · 소수 셋째 자리고쳐서 다시
invalid_address400연동 시작 · 잔액 조회 · 결제 요청SJO 주소 체크섬이 틀림고쳐서 다시
merchant_wallet_mismatch400결제 요청가맹점 지갑이 등록한 지급 주소와 다름고쳐서 다시
unauthorized401환율 밖 모두키가 없거나 틀림다시 보내지 마십시오 — 키 확인
merchant_inactive403환율 밖 모두승인된 가맹점이 아니거나 정지·삭제된 가맹점다시 보내지 마십시오 — 고객센터
not_found404연동 해제 · 구매 등록 · 매칭 전 취소 · 결제 상태 조회없는 결제 · 남의 결제, 해제할 연동이 없음. 없는 주소를 부르셔도 이것입니다다시 보내지 마십시오
wallet_not_linked404결제 요청구매자 지갑이 이 가맹점에 연동되지 않음다시 보내지 마십시오 — 먼저 지갑 연동
method_not_allowed405모두부르는 방식이 틀림 (예: POST 창구를 GET 으로). Allow 헤더에 맞는 방식이 있습니다고쳐서 다시
conflict409결제 요청같은 merchantRequestId 에 다른 내용다시 보내지 마십시오 — 새 결제면 새 키
wallet_link_conflict409연동 시작그 회원이 다른 주소로, 또는 그 주소가 다른 회원으로 연동됐거나 그런 요청이 살아 있음 (5분 안)다시 보내지 마십시오 — 해제 뒤 또는 마감 뒤 다시 시작
payment_in_flight409연동 해제 · 결제 요청그 지갑에 진행 중인 결제가 있음그 결제가 끝난 뒤 다시
buyer_busy409연동 해제 · 결제 요청같은 구매자의 다른 요청을 처리 중 — 같은 요청을 다시 보내 주십시오잠시 뒤 다시
wrong_mode409구매 등록방식이 api 가 아님다시 보내지 마십시오
payment_not_open409구매 등록결제가 거래중이 아니거나 취소 요청이 있음다시 보내지 마십시오
login_required409구매 등록지갑 앱 로그인 승인 전이거나 5분이 지남잠시 뒤 다시
already_matched409매칭 전 취소이미 매칭됨 — 결제는 그대로다시 보내지 마십시오
payment_finished409매칭 전 취소이미 거래완료다시 보내지 마십시오
wallet_undeliverable422연동 시작그 주소의 지갑 사용자가 없음다시 보내지 마십시오 — 주소 확인
rate_limited429모두호출 한도 초과 — retryAfterSeconds 를 함께 드립니다잠시 뒤 다시
internal500모두저희 쪽 문제입니다. 다시 부르셔도 됩니다잠시 뒤 다시
exchange_unavailable503연동 해제 · 결제 요청 · 구매 등록 · 매칭 전 취소거래소가 답하지 않음. 연동 해제 · 결제 요청은 처리하지 않았고, 구매 등록 · 매칭 전 취소는 받아 두고 저희가 다시 보냅니다. 다시 부르셔도 됩니다잠시 뒤 다시
wallet_unavailable503매칭 전 취소지갑이 답하지 않음 — 취소 요청은 받아 두고 저희가 다시 보냅니다. 다시 부르셔도 됩니다잠시 뒤 다시
chain_unavailable503잔액 조회 · 결제 요청체인을 읽지 못함 (결제 요청은 USDE 멈춤 상태) — 처리하지 않았습니다. 다시 부르셔도 됩니다잠시 뒤 다시
usde_paused503결제 요청USDE 가 멈춤잠시 뒤 다시
rate_unavailable503환율저장된 환율이 없음 — 이번 회차를 받지 못해도 앞서 저장된 값은 계속 드립니다잠시 뒤 다시

JSON 오류 본문 없이 오는 답도 있습니다 — 주소 안의 %2F · %5C 에 저희 웹 서버가 주는 400, Accept 가 JSON 을 받지 않을 때의 406 이 그렇습니다 (2절). HTTP 상태로 가르십시오.

사용자가 거래소 회원이 아니거나 거래소 계정으로 결제할 수 없는 경우는 오류가 아닙니다 — 연동 확인의 exchange.ready · reason 과, 결제의 CANCELLED 와 statusReason(not_member 등)으로 알려 드립니다.