개발자센터

LunePay 개발자센터

API 인증부터 주문·입금·매칭 연동까지 필요한 정보를 확인하세요.

개요

LunePay API는 무통장 입금 내역을 자동으로 수집하고, 등록된 주문과 실시간으로 매칭하는 기능을 제공합니다. 이 문서는 개발자가 LunePay 기능을 자신의 서비스에 연동하기 위한 상세한 가이드를 제공합니다.

Base URL

https://api.lunepay.app/api/v1

Content-Type

application/json

워크스페이스, 계좌, 주문, 입금, 매칭 리소스 ID는 모두 UUID 문자열입니다. 예시의 {id}는 해당 UUID로 바꿔서 사용하세요. 워크스페이스 API 키로 운영 데이터를 관리하는 v2 경로의 Base URL은 https://api.lunepay.app/api/v2입니다.

인증 (Authentication)

모든 요청은 Authorization: Bearer {credential} 형식을 사용합니다. 엔드포인트 종류에 따라 자격 증명이 다릅니다.

대상사용할 Bearer credential
대시보드/워크스페이스 API로그인으로 발급된 사용자 access token. 워크스페이스 멤버 권한이 적용됩니다.
v1 /orders, /bank-accounts 및 v2 /deposits, /orders, /matches워크스페이스 API 키. 키에 연결된 하나의 활성 워크스페이스에서만 동작하며, 대시보드 설정에서 확인하거나 소유자 권한으로 재발급합니다.

워크스페이스 API

워크스페이스는 계좌와 주문을 관리하는 최상위 단위입니다. 이 섹션의 엔드포인트는 사용자 access token과 워크스페이스 멤버 권한이 필요합니다.

워크스페이스 목록 조회

GET/workspaces

사용자가 소속된 모든 워크스페이스를 조회합니다.

워크스페이스 생성

POST/workspaces

새로운 워크스페이스를 생성합니다.

Request Body

JSON
{
  "name": "쇼핑몰 운영팀"
}

Response (201 Created)

JSON
{
  "id": "9d5d720a-cf07-4a82-b262-ef7b34d2cb73",
  "name": "쇼핑몰 운영팀",
  "owner_id": "4acb3c58-60c0-4700-8648-44d0e1be4b17",
  "is_active": true,
  "created_at": "2024-02-12T09:00:00+09:00"
}

생성한 사용자의 응답에는 워크스페이스 API 키도 포함됩니다. 이 키는 비밀 값으로 취급하고, 노출이 의심되면 소유자 권한으로 재발급하세요.

계좌 API

이 섹션의 계좌 설정 API는 워크스페이스 API 키로 인증합니다. URL에 워크스페이스 ID를 넣지 않으며, 키에 연결된 하나의 활성 워크스페이스 안에서만 동작합니다. 응답에는 전체 계좌번호·계좌비밀번호·빠른계좌조회 식별값·SMS 수신번호를 포함하지 않습니다.

등록 방식과 지원 은행

구분해당 은행등록 시 추가 값
빠른계좌조회 전용004 KB국민은행fast_lookup_password, fast_lookup_id_type, fast_lookup_id_value
일반 계좌 추가081 하나은행, 071 우체국계좌번호와 예금주
계좌 인증 필요003 IBK기업은행, 088 신한은행, 011 NH농협은행, 020 우리은행등록 후 대기열 기반 SMS 인증 절차

우리은행의 우리 MM/DD HH:MM → *계좌끝자리 → 입금/출금 → 상대방 → 잔액 SMS 형식도 지원합니다. 마스킹된 끝자리만 온 경우에는 해당 은행의 유일한 계좌일 때만 연결합니다.

빠른계좌조회 전용: KB국민은행

POST/bank-accounts

국민은행은 SMS 연동이 아니라 빠른계좌조회 자격정보로만 등록합니다.

Request Body

JSON
{
  "bank_code": "004",
  "account_number": "123-456-789012",
  "account_holder": "홍길동",
  "fast_lookup_password": "1234",
  "fast_lookup_id_type": "business",
  "fast_lookup_id_value": "123-45-67890"
}

Response (201 Created, 안전한 표시값)

JSON
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "004",
  "bank_name": "KB국민은행",
  "account_number_masked": "********9012",
  "registration_type": "fast_lookup",
  "verification_status": "not_required",
  "is_active": true,
  "created_at": "2024-02-12T10:00:00+09:00"
}

fast_lookup_id_typeid, business, birth 중 하나입니다. 사업자번호는 10자리, 생년월일은 YYMMDD 6자리이며 비밀번호와 식별값은 이후 어떤 API 응답에도 반환되지 않습니다.

일반 계좌 추가: 하나·우체국

POST/bank-accounts

하나은행(081), 우체국(071)은 별도 인증 절차 없이 등록합니다.

JSON
{
  "bank_code": "081",
  "account_number": "392-123456-14607",
  "account_holder": "홍길동"
}

계좌 인증이 필요한 은행: IBK기업·신한·NH농협·우리

IBK기업은행(003), 신한은행(088), NH농협은행(011), 우리은행(020)은 같은 POST /bank-accounts 요청으로 계좌 초안을 만들고 인증 대기열에 자동으로 등록합니다. 응답에는 verification 상태 객체와 registration_complete가 포함되며, is_active는 생성 시점부터 true입니다 — 인증은 registration_complete/verification_status로 진행 상황을 알려주는 참고(advisory) 정보일 뿐, 계좌 활성화나 SMS 처리 여부를 결정하지 않습니다. LunePay는 은행 쪽 등록 성공 여부를 자체적으로 확인할 수 없으므로, 이 상태들은 은행 등록 실패를 의미하지 않습니다.

POST/bank-accounts

계좌 초안을 생성하고 인증 대기열에 자동 등록합니다.

Request Body

JSON
{
  "bank_code": "088",
  "account_number": "110-123-456789",
  "account_holder": "홍길동"
}

Response (201 Created)

JSON
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "088",
  "bank_name": "신한은행",
  "account_number_masked": "*******6789",
  "registration_type": "verification_required",
  "registration_complete": false,
  "verification_status": "queued",
  "is_active": true,
  "is_usable": true,
  "restricted_reason": null,
  "verification": {
    "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
    "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
    "status": "queued",
    "phone_number": null,
    "verification_code": null,
    "verification_code_encrypted": null,
    "expires_at": null,
    "queue_position": 1,
    "estimated_wait_seconds": 240,
    "sequence": 1
  },
  "created_at": "2026-09-01T13:00:00+00:00"
}

새로 만든 계좌는 인증 완료 여부와 무관하게 is_active: true로 생성되며, 플랜에 여유가 있으면 is_usabletrue입니다(위 예시는 제한 없는 워크스페이스 기준). registration_complete: falseverification_status: "queued"는 은행 등록 절차가 아직 진행 중이라는 참고 정보일 뿐, 계좌를 사용할 수 없거나 SMS 처리가 막혔다는 뜻이 아닙니다. 다만 실제로 SMS 알림이 도착하려면 고객이 은행 쪽 SMS 통지서비스 수신번호 변경을 실제로 마쳐야 합니다.

인증 대기열과 상태 조회

대기열은 API와 대시보드가 함께 사용하는 전역 큐입니다. queue_position이 0이면 현재 진행 중인 순서이고, 그 밖의 값은 앞으로 몇 번째로 시작되는지를 뜻합니다. phone_numberexpires_at은 현재 진행 중인 순서(queue_position: 0)에만 채워지고, 대기 중에는 null입니다.

POST/bank-accounts/{bank_account_id}/verification/start

대기열에 등록하거나, 취소·만료된 시도를 재시작합니다. 본문 없이 호출합니다.

GET/bank-accounts/{bank_account_id}/verification

현재 시도의 상태를 조회합니다.

JSON
{
  "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
  "status": "code_received",
  "phone_number": "010-0000-0000",
  "verification_code": "123456",
  "verification_code_encrypted": null,
  "expires_at": "2026-09-01T13:05:00+00:00",
  "queue_position": 0,
  "estimated_wait_seconds": 0,
  "sequence": 3
}

statusqueuedreadypendingcode_received 순으로 진행하며, verified/cancelled/expired로 끝납니다. ready가 된 시점부터 300초 안에 완료해야 하며, 이 300초는 sms-sent 호출이나 코드 수신, 반복 조회로 연장되지 않습니다. sequence는 계좌 하나를 기준으로 모든 시도에 걸쳐 계속 증가하는 값입니다 — 새 시도를 시작해도 다시 1부터 시작하지 않으므로, 오래된 응답이나 지연 도착한 웹훅을 걸러내는 데 그대로 사용할 수 있습니다.

기본값(워크스페이스가 인증번호 암호화를 켜지 않은 상태)에서는 코드가 수신되면 이 API 응답과 웹훅 모두 verification_code에 원문이 그대로 담깁니다. 워크스페이스가 인증번호 암호화를 활성화한 경우에만 verification_code가 항상 null로 바뀌고 대신 verification_code_encrypted가 채워집니다 — 자세한 내용은 아래 "인증번호 암호화 수신" 절을 참고하세요. LunePay 대시보드의 인증 화면은 이 설정과 무관하게 항상 원문 인증번호를 보여줍니다.

인증 진행: 인증번호 요청 확인 → 취소/완료

POST/bank-accounts/{bank_account_id}/verification/sms-sent

고객이 표시된 번호로 은행에 인증번호(SMS) 발송을 요청했음을 알립니다. status를 ready에서 pending으로 바꿀 뿐이며, 이 호출 자체가 은행에 인증번호를 보내지는 않습니다. SMS 통지서비스(계좌) 등록 자체는 이 단계가 아니라 complete가 성공해야 최종 완료됩니다.

JSON
{
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"
}
POST/bank-accounts/{bank_account_id}/verification/cancel

진행 중인 대기열 순서를 종료합니다.

JSON
{
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"
}
POST/bank-accounts/{bank_account_id}/verification/complete

은행 앱/ARS에 인증번호 입력까지 마친 뒤, 계좌 등록 완료를 확정합니다. code_received 이전에는 완료할 수 없습니다.

JSON
{
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"
}

complete가 성공하면 registration_complete: true, verification_status: "verified"로 바뀝니다. is_active는 계좌 생성 시점부터 이미 true였다면 그대로 유지되며, complete 성공 여부는 is_active나 플랜별 is_usable을 바꾸지 않습니다.

하위 호환을 위해 POST /bank-accounts/{bank_account_id}/verification/start는 과거처럼 본문에 handshake_token을 담아 호출하는 것도 계속 지원하며, POST /bank-accounts/{bank_account_id}/verification/codeverification_code를 담아 호출해도 완료 처리됩니다. 신규 연동은 verification_id 기반의 sms-sent/cancel/complete를 사용하세요.

인증번호 암호화 수신 (선택)

기본값은 비활성화이며, 이 상태에서는 코드가 수신되는 즉시 API 응답과 웹훅 모두 verification_code에 원문을 담아 반환합니다. 활성화하려면 순서가 중요합니다 — 워크스페이스 소유자가 대시보드 설정에서 먼저 시크릿을 발급받아 안전하게 저장한 뒤에 암호화를 켜야 합니다(시크릿 없이는 켤 수 없습니다). 켠 이후부터는 verification_code가 항상 null이 되고 대신 verification_code_encrypted에 아래 형식의 값이 채워집니다. 어느 쪽이든 대시보드의 인증 화면은 항상 원문을 보여줍니다.

대시보드 조작 순서: 워크스페이스 선택 → 워크스페이스 설정 → "입금 인증코드 암호화" 카드 → 키 생성(이미 키가 있다면 키 재발급) → 화면에 한 번만 표시되는 시크릿과 key_id를 그 자리에서 서버(연동 서버)쪽에만 저장 → 저장을 확인한 뒤 활성화 버튼으로 암호화를 켭니다. 시크릿은 재조회할 수 없으므로 이 순서를 건너뛰고 먼저 활성화부터 누르면 안 됩니다.

이 시크릿은 워크스페이스 설정의 API 키와는 완전히 별개로 관리됩니다. 암호화를 켜거나 끄거나 시크릿을 재발급해도 아웃바운드 웹훅 서명(X-LunePay-Signature, API 키 기반 HMAC)에는 어떤 영향도 없습니다.

JSON
{
  "algorithm": "A256GCM",
  "key_id": "b2b5f9b0-2a34-4a3f-9c7e-2b6b7b9a6b21",
  "iv": "<base64, 12바이트 랜덤 IV>",
  "ciphertext": "<base64, 암호문 + 마지막 16바이트 GCM 태그>",
  "aad": "<base64, lunepay:bank-verification:{workspace_id}:{bank_account_id}:{verification_id}>"
}

iv, ciphertext, aad와 발급되는 시크릿만 표준 Base64입니다. algorithm은 알고리즘 이름 문자열("A256GCM" 고정)이고 key_id는 Base64가 아닌 키 식별자 문자열입니다. 시크릿은 발급 시 한 번만 평문으로 응답에 포함되는 32바이트 AES-256 키(표준 Base64)이며, 이후에는 다시 조회할 수 없으므로 즉시 안전하게 보관하세요. key_id는 시크릿을 재발급하면 바뀌며, 이전 key_id로 암호화된 값은 새 시크릿으로 복호화할 수 없습니다.

A256GCM(AES-256-GCM)의 정확한 동작은 cryptography 라이브러리의 AESGCM 공식 문서를 참고하세요.

Node.js 복호화 예시
const { createDecipheriv } = require("crypto");

// secretBase64: 워크스페이스 시크릿 발급 응답의 secret 값. 서버 환경에만 보관하고
// 브라우저/클라이언트 코드에는 절대 포함하지 마세요.
function decryptVerificationCode({ ciphertext, iv, aad }, secretBase64) {
  const key = Buffer.from(secretBase64, "base64");     // 32바이트 AES-256 키
  const ivBuf = Buffer.from(iv, "base64");              // 12바이트 IV
  const combined = Buffer.from(ciphertext, "base64");   // 암호문 + 마지막 16바이트 태그
  const authTag = combined.subarray(combined.length - 16);
  const encrypted = combined.subarray(0, combined.length - 16);

  const decipher = createDecipheriv("aes-256-gcm", key, ivBuf);
  decipher.setAAD(Buffer.from(aad, "base64"));
  decipher.setAuthTag(authTag);

  const plaintext = Buffer.concat([decipher.update(encrypted), decipher.final()]);
  return plaintext.toString("utf8"); // 예: "123456"
}

aadlunepay:bank-verification:{workspace_id}:{bank_account_id}:{verification_id} 문자열의 Base64 인코딩입니다. 복호화 전 setAAD에 그대로 전달해야 GCM 인증이 통과합니다. 복호화에 성공한 뒤에도 디코딩한 aad 문자열이 실제로 기대하는 workspace_id·bank_account_id·verification_id와 일치하는지 직접 비교해, 다른 레코드의 값을 잘못 신뢰하는 일이 없도록 하세요.

이 복호화 코드는 "올바른 키로 암호화된 값을 안전하게 여는 최소한의 절차"일 뿐, 그 자체로 지금 요청한 사람이 이 계좌의 실제 고객인지를 인증해주지 않습니다. 복호화에 성공했다는 사실과 별개로, 그 인증번호를 어떤 고객 화면에 보여줄지는 위 은행 계좌 인증 매뉴얼의 사전 준비에서 설명한 대로 연동 서버가 자체 세션/권한 확인을 거쳐 결정해야 합니다.

조회와 활성 상태 변경

GET/bank-accounts?is_active=true

현재 API 키의 워크스페이스 계좌만 조회합니다. is_active=false를 붙이면 현재 비활성 상태인 계좌만 조회합니다(이 릴리스 이전에 다른 이유로 비활성 처리된 기존 레코드가 있다면, 그런 레코드도 그대로 포함되며 활성으로 일괄 전환되지 않습니다).

GET/bank-accounts/{bank_account_id}

두 응답 모두 is_active, is_usable, restricted_reason, verification_status, registration_complete를 포함합니다. is_active는 아래 activate/deactivate 호출로만 바뀌며, 인증 진행 상태(verification_status/registration_complete)나 플랜 자격(is_usable)과는 독립적입니다.

PATCH/bank-accounts/{bank_account_id}/deactivate

본문 없이 호출합니다. is_active를 false로 바꿉니다.

Request
curl -X PATCH https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/deactivate \
  -H "Authorization: Bearer lpay_발급받은_API_키"
Response (200 OK)
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "088",
  "bank_name": "신한은행",
  "account_number_masked": "*******6789",
  "registration_type": "verification_required",
  "registration_complete": true,
  "verification_status": "verified",
  "is_active": false,
  "is_usable": false,
  "restricted_reason": "비활성 계좌입니다.",
  "created_at": "2026-09-01T13:00:00+00:00"
}

위 예시는 인증이 이미 verified로 끝난 계좌를 비활성화하는 상황을 보여줄 뿐입니다 — 아래 activate 예시(queued)는 같은 계좌의 다음 단계가 아니라 별개의 계좌/상황을 보여주는 독립적인 예시입니다. 진행 중인 인증 대기열 순서가 있다면 deactivate가 그 시도를 취소하지만, 고객이 이미 은행 쪽에서 신청/변경한 SMS 통지서비스 설정 자체를 되돌리지는 않습니다. 이후 인증이 다시 complete되더라도 이 명시적 비활성화 선택은 유지되며, is_active를 되돌리려면 아래 activate 호출이 필요합니다.

PATCH/bank-accounts/{bank_account_id}/activate

본문 없이 호출합니다. is_active를 true로 바꿉니다.

Request
curl -X PATCH https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/activate \
  -H "Authorization: Bearer lpay_발급받은_API_키"
Response (200 OK)
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "088",
  "bank_name": "신한은행",
  "account_number_masked": "*******6789",
  "registration_type": "verification_required",
  "registration_complete": false,
  "verification_status": "queued",
  "is_active": true,
  "is_usable": true,
  "restricted_reason": null,
  "created_at": "2026-09-01T13:00:00+00:00"
}

activate는 인증 상태나 플랜 자격을 확인하거나 바꾸지 않습니다 — 인증이 아직 진행 중이거나(queued/ready/pending/code_received) 끝난(cancelled/expired) 계좌도 activateis_active: true가 될 수 있습니다. 이 호출은 은행에 어떤 요청도 보내지 않고 verification_statusverified로 바꾸지도 않습니다. 플랜 한도로 is_usable: false인 계좌는 activate 이후에도 is_usable: false인 채로 남을 수 있습니다 — 그 계좌를 실제로 쓰려면 플랜 자체의 제한을 해소해야 합니다.

대시보드용 /workspaces/{id}/bank-accounts API는 사용자 access token과 멤버 권한을 계속 사용합니다. 외부 연동에는 이 섹션의 API 키 경로를 사용하세요.

은행 계좌 인증 매뉴얼

IBK기업(003)·신한(088)·NH농협(011)·우리(020) 네 은행은 계좌 등록 후 은행 SMS 인증 절차를 거쳐야 최종 활성화됩니다. 이 절차는 은행이 발급하는 하드웨어 OTP 기기와는 무관하며, 그 은행의 SMS 입금통지 수신번호 변경 신청을 매개로 한 인증입니다. 이 절만 따라가면 계좌 초안 생성부터 등록 완료까지 필요한 API 호출과, 고객이 은행에서 직접 진행해야 하는 절차를 순서대로 확인할 수 있습니다.

은행별 등록 방식 요약

은행코드registration_type설명
IBK기업은행003verification_required계좌 등록 후 은행 SMS 인증 필요
신한은행088verification_required계좌 등록 후 은행 SMS 인증 필요
NH농협은행011verification_required계좌 등록 후 은행 SMS 인증 필요
우리은행020verification_required계좌 등록 후 은행 SMS 인증 필요
하나은행081standardSMS 알림 등록만 필요, 별도 인증 절차 없음
우체국071standardSMS 알림 등록만 필요, 별도 인증 절차 없음
KB국민은행004fast_lookupSMS 연동이 아닌 빠른계좌조회 자격정보 등록

사전 준비

  • 워크스페이스 API 키는 하나의 워크스페이스에만 연결된 서버 간 통신 전용 자격 증명입니다(Authorization: Bearer lpay_...). API 키와 인증번호 암호화 시크릿 모두 브라우저·모바일 클라이언트 코드에는 절대 포함하지 말고, 파트너의 서버 안에서만 보관·사용하세요.
  • 이 절의 계좌·인증 엔드포인트는 모두 Base URL https://api.lunepay.app/api/v1을 사용합니다. 다른 파트너 API가 v2를 쓰더라도 계좌/인증 API는 v1입니다.
  • LunePay API는 워크스페이스 단위로만 인증합니다 — 특정 bank_account_id가 지금 요청한 그 고객의 것이 맞는지 확인하는 책임은 연동 서버에 있습니다. 파트너 사이트는 자체 로그인/세션으로 고객과 계좌를 먼저 매핑하고, 그 확인이 끝난 뒤에만 상태·인증번호를 고객 화면에 표시하세요.
  • 이 절차는 워크스페이스 API 키만으로 끝까지 완료할 수 있으며 LunePay 대시보드 로그인은 필요하지 않습니다. 다만 은행 앱/ARS 로그인은 항상 고객 본인의 로그인을 그대로 사용해야 하며, 연동 서버나 LunePay가 이를 대신할 수 없습니다.
  • 웹훅 URL 등록은 선택입니다 — 폴링만으로도 전체 절차를 완료할 수 있습니다(워크스페이스 웹훅 URL은 워크스페이스 설정에서 관리합니다).
  • 인증번호 암호화 시크릿은 선택 기능이며 API 키와는 무관한 별도 값입니다. 자세한 내용은 아래 인증번호 암호화 수신 절을 참고하세요.

고객·연동 서버 책임 분담

순서수행 주체작업
1연동 서버POST /bank-accounts 호출로 계좌 초안 생성 (자동으로 인증 대기열 등록)
2연동 서버응답의 id(bank_account_id)와 verification.verification_id 저장
3연동 서버GET /bank-accounts/{bank_account_id}/verification 폴링 또는 웹훅 수신으로 상태 대기
4고객상태가 ready가 되면 응답에 표시된 phone_number로 은행 앱/ARS에서 SMS 통지서비스 수신번호 변경 신청
5연동 서버고객이 신청을 마치면 POST /verification/sms-sent 호출로 확인 통지
6연동 서버GET /verification 또는 웹훅으로 code_receivedverification_code 확인
7고객LunePay 화면(또는 API 응답)에 표시된 인증번호를 은행 앱/ARS 쪽에 입력해 그 은행 쪽 SMS 통지서비스 등록 완료 — 은행이 인증번호를 보여주는 것이 아니라, 고객이 LunePay가 받은 인증번호를 은행에 입력하는 순서입니다
8연동 서버POST /verification/complete 호출로 등록 최종 확정

201 응답이나 registration_complete: false는 등록 실패가 아니라 정상적인 중간 상태입니다 — 이 시점에도 is_active는 이미 true입니다. 초기 응답은 바로 ready일 수도, queued로 대기 중일 수도 있습니다 — 어느 경우든 별도의 시작(start/handshake) 호출은 필요하지 않습니다.

1. 계좌 초안 생성 (자동 인증 대기열 등록)

POST/bank-accounts

계좌 초안을 만들고 동시에 인증 대기열에 자동 등록합니다.

Request
curl -X POST https://api.lunepay.app/api/v1/bank-accounts \
  -H "Authorization: Bearer lpay_발급받은_API_키" \
  -H "Content-Type: application/json" \
  -d '{"bank_code":"088","account_number":"110-123-456789","account_holder":"홍길동"}'
Response (201 Created)
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "088",
  "bank_name": "신한은행",
  "account_number_masked": "*******6789",
  "registration_type": "verification_required",
  "registration_complete": false,
  "verification_status": "queued",
  "is_active": true,
  "is_usable": true,
  "restricted_reason": null,
  "verification": {
    "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
    "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
    "status": "queued",
    "phone_number": null,
    "verification_code": null,
    "verification_code_encrypted": null,
    "expires_at": null,
    "queue_position": 1,
    "estimated_wait_seconds": 240,
    "sequence": 1
  },
  "created_at": "2026-09-01T13:00:00+00:00"
}

최상위 id는 계좌(bank_account_id)이고, verification.verification_id는 이번 인증 시도의 ID로 서로 다른 값입니다. 시도를 재시작하면 계좌 id는 그대로지만 verification_id는 새로 발급됩니다 — 이후 모든 sms-sent/cancel/complete 호출에는 현재 유효한 verification_id를 사용해야 합니다. is_active: true는 인증 완료 여부와 무관하게 계좌 생성 시점부터 유지되는 값입니다.

2. 대기 상태 확인 (대기열 → 순서 도달)

GET/bank-accounts/{bank_account_id}/verification

현재 시도의 상태를 조회합니다. 응답에 캐시되지 않도록 no-store가 설정됩니다.

Response — 순서 도달(ready)
{
  "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
  "status": "ready",
  "phone_number": "010-0000-0000",
  "verification_code": null,
  "verification_code_encrypted": null,
  "expires_at": "2026-09-01T13:05:00+00:00",
  "queue_position": 0,
  "estimated_wait_seconds": 0,
  "sequence": 2
}

expires_atready가 된 시점부터 정확히 300초 뒤이며, 전체 대기 시간이나 sms-sent 호출 시점을 기준으로 하지 않습니다. queue_position: 0은 "지금 조작 가능한 차례"가 아니라 "대기열에서 더 앞에 없음"을 뜻할 뿐입니다 — 이미 완료·취소·만료된 시도에서도 0일 수 있으므로, 조작 가능 여부는 반드시 statusready/pending/code_received 중 하나인지로 판단하세요. phone_number는 항상 지금 계좌 등록 화면에 표시된 최신 값을 사용하고, 예시 번호나 다른 계좌의 번호를 사용하지 마세요.

3. 고객이 은행에서 SMS 통지서비스 수신번호 변경 신청

고객이 해당 은행 앱 또는 ARS의 입금 알림(SMS 통지서비스) 수신번호 변경 메뉴에서, 위에서 조회한 phone_number로 변경을 신청하고 SMS 발송을 요청합니다. 이는 은행 앱의 푸시 알림이 아니라 실제 SMS 문자 수신 설정입니다. 은행마다 메뉴 이름과 위치, 접수 전화번호가 다르므로 이 문서는 특정 은행의 정확한 메뉴 경로나 전화번호를 안내하지 않습니다 — 은행별 화면 흐름은 계좌 인증 설정 가이드를 참고하세요.

4. 발송 확인 통지

POST/bank-accounts/{bank_account_id}/verification/sms-sent

고객이 은행 쪽 신청을 마쳤음을 LunePay에 알립니다.

JSON
{
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"
}

이 호출은 확인 통지일 뿐입니다 — 이 호출 자체가 은행에 SMS를 발송하지 않고, 인증번호를 제출하지도, 등록을 완료하지도 않습니다. 신청 직후 SMS가 이 호출보다 먼저 도착한 경우 응답이 곧바로 code_received로 반환될 수 있습니다(도착한 코드는 ready 상태에서 내부적으로 버퍼링되어 있다가 이 확인 시점에 노출됩니다). pending/code_received 상태에서 다시 호출하면 오류 없이 현재 상태를 그대로 반환하며 300초 타이머를 다시 연장하지 않습니다. ready가 아닌 다른 상태에서 처음 호출하면 409, 이미 만료됐다면 410이 반환됩니다.

5. 인증번호 수신 확인

GET/bank-accounts/{bank_account_id}/verification

code_received 상태와 인증번호를 확인합니다.

Response — 코드 수신(code_received)
{
  "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
  "status": "code_received",
  "phone_number": "010-0000-0000",
  "verification_code": "048213",
  "verification_code_encrypted": null,
  "expires_at": "2026-09-01T13:05:00+00:00",
  "queue_position": 0,
  "estimated_wait_seconds": 0,
  "sequence": 3
}

verification_code는 항상 문자열이며 은행에 따라 4~6자리입니다 — 앞자리 0이 있는 값(예: 위 예시의 048213)을 정수로 변환하지 말고 문자열 그대로 다루세요. 워크스페이스가 인증번호 암호화를 켰다면 이 값은 null이 되고 대신 verification_code_encrypted가 채워집니다(아래 인증번호 암호화 수신 절 참고). 고객은 이 인증번호를 LunePay가 아니라 은행 앱/ARS 화면에 입력해 그 은행 쪽 SMS 통지서비스 등록을 완료해야 합니다. LunePay는 은행 쪽 완료 여부를 자체적으로 조회하지 않으므로, 코드가 도착했다는 사실만으로 등록을 자동 완료 처리하지 않습니다. 파트너 사이트는 이 인증번호를 고객 화면에 보여주기 전에, 그 bank_account_id가 지금 로그인한 그 고객의 것이 맞는지 자체 세션/권한 확인을 먼저 거쳐야 합니다.

6. 등록 완료 확정

POST/bank-accounts/{bank_account_id}/verification/complete

은행 쪽 등록을 마쳤다는 것을 확인하고 계좌 등록을 최종 확정합니다.

JSON
{
  "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"
}
Response (200 OK)
{
  "id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "bank_code": "088",
  "bank_name": "신한은행",
  "account_number_masked": "*******6789",
  "registration_type": "verification_required",
  "registration_complete": true,
  "verification_status": "verified",
  "is_active": true,
  "is_usable": true,
  "restricted_reason": null,
  "verification": {
    "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
    "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
    "status": "verified",
    "phone_number": null,
    "verification_code": null,
    "verification_code_encrypted": null,
    "expires_at": "2026-09-01T13:05:00+00:00",
    "queue_position": 0,
    "estimated_wait_seconds": 0,
    "sequence": 4
  },
  "created_at": "2026-09-01T13:00:00+00:00"
}

본문은 verification_id이며 verification_code가 아닙니다 — 현재 유효한 verification_id와 일치하지 않으면 409가 반환됩니다. code_received 이전 상태에서 호출하면 마찬가지로 409, 이미 만료됐다면 410입니다. 성공하면 registration_type: "verification_required", verification_status: "verified", registration_complete: true로만 바뀝니다 — 이 호출은 로컬 인증 기록(참고 정보)만 완료 처리할 뿐입니다. is_active는 이 호출로 바뀌지 않고 호출 전 값 그대로 유지됩니다(계좌 생성 시점부터 true였다면 그대로 true이고, 그 사이 누군가 명시적으로 deactivate했다면 false인 채로 남습니다). 플랜별 is_usable/restricted_reason도 이 호출로 바뀌지 않습니다(예: 계좌 한도 초과로 is_usable: false였다면 그대로 유지). phone_number·verification_code는 완료된 시도에서 더 이상 노출되지 않고, expires_atready 시점의 마지막 값을 그대로 유지합니다. 계좌 자체는 최초 POST /bank-accounts 시점에 이미 생성되어 있으며, 이 호출은 로컬 인증 기록을 확정할 뿐 계좌 활성화나 SMS 처리 여부를 좌우하지 않습니다. 이미 verified인 같은 시도에 complete를 다시 호출해도 안전하게 같은 결과를 반환합니다.

curl 전체 시퀀스 (복사해서 사용)

아래는 각 단계를 한 곳에 모아 보여주는 단계별 참조이지, 위에서 아래로 한 번에 실행하는 스크립트가 아닙니다. 1)과 2) 사이에는 상태가 ready가 될 때까지, 2)와 3) 사이에는 고객이 실제로 은행에 SMS 발송을 요청할 때까지, 3)과 4) 사이에는 code_received가 되고 고객이 은행 쪽 등록을 실제로 마쳤다고 확인할 때까지 각각 기다려야 합니다. cancelrestart는 4)에 이어서 순서대로 실행하는 것이 아니라, 상황에 따라 택하는 대안 분기입니다 — 이미 complete가 성공한 뒤 이어서 호출할 명령이 아닙니다. 아래 bank_account_id·verification_id·API 키 값은 모두 예시이며, 실제 연동에서는 각 단계의 실제 응답에서 저장한 값으로 바꿔서 사용하세요.

create → status → sms-sent → complete (+ cancel / restart, 참고용 — 실제로는 각 단계 사이에 대기 필요)
# 1) 계좌 초안 생성 + 인증 대기열 자동 등록
curl -X POST https://api.lunepay.app/api/v1/bank-accounts \
  -H "Authorization: Bearer lpay_발급받은_API_키" \
  -H "Content-Type: application/json" \
  -d '{"bank_code":"088","account_number":"110-123-456789","account_holder":"홍길동"}'

# 2) 상태 조회 (대기 / 순서 도달 / 코드 수신 확인 — 필요한 만큼 반복 호출)
curl https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/verification \
  -H "Authorization: Bearer lpay_발급받은_API_키"

# 3) 고객의 은행 SMS 통지서비스 신청 완료 후, 발송 확인 통지
curl -X POST https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/verification/sms-sent \
  -H "Authorization: Bearer lpay_발급받은_API_키" \
  -H "Content-Type: application/json" \
  -d '{"verification_id":"6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"}'

# 4) 인증번호 수신 후, 고객이 은행 앱/ARS에 입력을 마쳤다면 등록 확정
curl -X POST https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/verification/complete \
  -H "Authorization: Bearer lpay_발급받은_API_키" \
  -H "Content-Type: application/json" \
  -d '{"verification_id":"6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"}'

# (선택) 대기/진행 중인 시도를 취소 — 초안 계좌를 지우거나 은행 쪽 설정을 되돌리지 않음
curl -X POST https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/verification/cancel \
  -H "Authorization: Bearer lpay_발급받은_API_키" \
  -H "Content-Type: application/json" \
  -d '{"verification_id":"6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90"}'

# (선택) 취소/만료된 시도를 재시작 — 본문 없이 호출. 살아있는 시도(queued/ready/pending/code_received)가
# 있으면 그 시도를 타이머/대기열 순서 그대로 반환하고, cancelled/expired인 경우에만 verification_id가
# 새로 발급됩니다. 이미 verified인 계좌에서 호출하면 409가 반환됩니다.
curl -X POST https://api.lunepay.app/api/v1/bank-accounts/0f3757be-2e0a-4f20-b491-4fdddb1dd1d5/verification/start \
  -H "Authorization: Bearer lpay_발급받은_API_키"

요청이 타임아웃되거나 응답을 못 받았다면 같은 계좌를 다시 POST /bank-accounts로 생성하지 말고, 저장해 둔 bank_account_id로 먼저 GET /bank-accounts/{bank_account_id}/verification을 호출해 현재 상태를 복구하세요. 반복된 계좌 생성 요청이 멱등이라는 보장은 없습니다.

bank_account_id조차 저장하지 못한 채 POST /bank-accounts 응답을 통째로 잃어버렸다면, GET /bank-accounts(전체 목록, 필터 없이)로 워크스페이스의 계좌 목록을 조회해 그 요청 시점의 은행 코드·마스킹된 계좌번호(끝 4자리)·생성 시각 같은 로컬에 남아있는 요청 정보와 대조해 후보를 좁히세요. 마스킹된 계좌번호는 끝 4자리만 보여주므로 여러 계좌가 같은 값을 가질 수 있습니다 — 후보가 하나로 좁혀지지 않으면 추측으로 특정 계좌를 완료 처리하지 말고, 다시 명확히 식별될 때까지 대기하거나 지원팀에 문의하세요. 반대로 bank_account_id는 이미 알고 있다면 재시도 전에 먼저 그 ID로 GET .../verification을 호출해 현재 시도가 이미 진행 중인지부터 확인하세요. POST /verification/complete는 이미 verified인 같은 시도에 다시 호출해도 안전하게 같은 결과를 반환하지만(재시도 가능), 최초의 POST /bank-accounts 계좌 생성 자체는 멱등이 보장되지 않습니다.

상태·조작 가능 여부

status의미지금 가능한 조작다음 전환
queued대기열에서 순서를 기다리는 중GET(상태 조회)·cancel 가능 — sms-sent/complete는 아직 불가ready
ready지금 차례 — 은행 SMS 신청 필요GET·cancel·sms-sent 가능pending 또는 code_received
pending발송 확인 접수, 코드 도착 대기 중GET·cancel 가능(코드 도착까지 대기) — sms-sent 재호출은 오류 없이 현재 상태만 반환code_received
code_received인증번호 수신 완료GET·cancel·complete 가능verified
verified룬페이 인증 절차 완료 (참고)GET만 의미 있음 — 완료됨, 추가 조작 불필요
cancelled고객/서버가 취소함GET 가능, 그 외에는 재시작(verification/start) 필요재시작(verification/start)
expired300초 데드라인 초과GET 가능, 그 외에는 재시작(verification/start) 필요재시작(verification/start)

위 표의 상태·조작은 오직 이 인증 절차 자체에 대한 것이며, 계좌 활성화와는 별개입니다 — cancel·만료(expiredcomplete 중 무엇도 계좌의 is_active를 바꾸지 않습니다. cancel은 호출 시점에 유효한 verification_id로만 가능하며, 초안 계좌를 삭제하거나 이미 신청한 은행 쪽 설정을 되돌리지 않습니다. 만료·취소·완료는 모두 대기열의 다음 대기자를 진행시킵니다. verification/start를 본문 없이 호출했을 때의 동작은 현재 시도 상태에 따라 다릅니다 — queued/ready/pending/code_received처럼 아직 살아있는 시도가 있다면 같은 verification_id와 타이머·대기열 순서를 그대로 유지한 채 그 시도를 그대로 반환합니다(새로 시작되지 않습니다). cancelled/expired처럼 끝난 시도만 새 verification_id를 발급받아 대기열에 다시 등록됩니다. 이미 verified인 계좌에서 호출하면 409가 반환됩니다. sequence는 계좌 하나를 기준으로 계속 증가하는 값이라 재시작해도 1로 리셋되지 않습니다. cancelled/expired 시도는 verification_code·phone_number가 비워지지만 expires_at은 마지막 값이 과거 시각으로 그대로 남아 있을 수 있습니다 — queued로 새로 시작한 시도만 expires_at: null이 보장됩니다.

웹훅으로 실시간 수신

워크스페이스에 웹훅 URL을 등록해 두면 bank_account.verification.updated 이벤트로 상태 변화를 받을 수 있습니다. 페이로드 형식과 서명 방식은 위 웹훅 섹션과 동일합니다(HMAC-SHA256, 워크스페이스 API 키가 secret).

  • X-LunePay-Event-Id로 중복을, 계좌별로 계속 증가하는 data.sequence로 낡은 이벤트를 걸러내세요. 이벤트의 verification_id가 현재 진행 중인 시도와 다르면 이미 지나간 시도의 이벤트입니다.
  • LunePay는 이미 다음 단계로 넘어갔거나 만료된 시도의 이벤트(중간 단계의 queued/ready 알림 포함)를 발송 시점에 억제합니다. 즉 모든 중간 상태 전이가 반드시 도착한다는 보장은 없으므로, 상태가 건너뛰어 도착할 수 있다는 전제로 처리하고 필요하면 GET으로 보정하세요.
  • 재시도(최초 발송 포함 최대 4회, 30초·2분·10분 간격)가 실제로 전송될 때마다 그 시점의 워크스페이스 암호화 설정/키로 verification_code_encrypted를 다시 계산합니다 — 같은 이벤트의 재시도라도 암호문 바이트가 달라질 수 있으므로 암호문 비교가 아니라 이벤트 ID/시퀀스로 중복을 제거하세요. 또한 재시도가 도착하기 전에 시도가 만료되거나 다음 시도로 대체되면 그 뒤의 재시도는 전송되지 않을 수 있습니다 — 낡은 인증번호의 지연 전달을 보장하지 않습니다.
  • 웹훅 없이 폴링만으로 연동한다면 GET /bank-accounts/{bank_account_id}/verification을 3~5초 간격 정도로 호출하는 것을 권장합니다(계약상의 속도 제한은 아닙니다). statusverified/cancelled/expired 중 하나가 되면 폴링을 멈추세요.
  • estimated_wait_seconds는 대략적인 대기열 예상치일 뿐입니다 — 전체 대기열에 5분 상한이 있는 것은 아니며, 300초 데드라인은 오직 ready가 된 이후에만 적용됩니다.

문제 해결 체크리스트

증상 / 상태 코드원인조치
409본문의 verification_id가 현재 유효한 시도와 다름(낡은 attempt)GET /verification으로 최신 상태와 verification_id를 다시 조회
409잘못된 순서 호출(예: ready 이전에 sms-sent, code_received 이전에 complete)현재 status를 확인한 뒤 올바른 순서로 재호출
410ready 시점부터 300초 데드라인 초과POST /verification/start(본문 없음)로 재시작
422Authorization 헤더 누락(필수 헤더로 선언됨) 또는 요청 형식 오류(UUID/enum 등)요청 헤더·본문 형식 확인
401Bearer 형식이 잘못되었거나 API 키가 유효하지 않음API 키 값과 Bearer 접두사 확인
403워크스페이스 소유자/멤버 권한이 필요한 대시보드 전용 설정(예: 인증번호 암호화 설정)에 권한 없이 접근워크스페이스 역할 확인 — 파트너 API 키 자체가 유효하지 않은 경우는 401입니다
404다른 워크스페이스의 계좌 ID, 또는 API 키 파트너 표면이 애초에 노출하지 않는 계좌(대시보드 전용 레거시 계좌 등)bank_account_id와 사용 중인 API 키의 워크스페이스 확인
400인증 관련 엔드포인트(sms-sent/cancel/complete/verification/start 등)를 계좌 인증이 필요 없는 은행(004/081/071)의 계좌에 호출함KB국민은행(004)은 빠른조회 등록 절차를, 하나은행(081)·우체국(071)은 SMS 알림 등록 절차를 따르세요. 이 은행들은 계좌 인증 엔드포인트를 호출하지 않습니다.
400이 계좌에 배정할 활성 SMS 수신 번호가 없음잠시 후 재시도하거나 지원팀에 문의
402플랜 구독 필요 또는 워크스페이스 계좌 한도 초과플랜 업그레이드 또는 기존 계좌 정리 후 재시도
SMS를 못 받음수신번호가 최신 phone_number와 다르거나, 푸시 알림으로 착각했거나, sms-sent 확인을 보내지 않았거나, 300초가 지났거나, 은행 SMS 발송이 지연됨phone_number 최신값 재확인 → 실제 SMS(문자) 수신함 확인 → sms-sent 호출 여부 확인 → 남은 시간 확인 → 은행 발송 지연 가능성을 고려해 잠시 대기 후 재시도

고객에게 인증번호나 암호화 시크릿을 로그, 채팅, 이메일로 전달하도록 요청하지 마세요. 인증번호는 API/웹훅으로, 시크릿은 대시보드 발급 화면에서 한 번만 직접 확인하는 것이 정상 경로입니다.

대시보드에서 진행하는 경우

외부 연동 없이 LunePay 대시보드에서 직접 등록할 때도 같은 대기열과 상태를 화면으로 보여줍니다: 대기 화면(queued) → 순서 도달 시 수신번호 표시(ready) → 고객이 은행에서 SMS 신청 → 대시보드의 "인증번호 요청 완료" 버튼(sms-sent에 해당) → 인증번호 표시(code_received) → 고객이 그 번호를 은행 앱/ARS에 입력 → 대시보드의 별도 "인증완료" 버튼(complete에 해당)을 누르면 로컬 참고용 인증 기록이 완료로 표시됩니다. 계좌는 이 버튼을 누르기 전부터 이미 활성 상태이며, 은행에서 들어오는 입금 SMS는 이 버튼과 무관하게 처리됩니다. 화면 스크린샷은 계좌 인증 설정 가이드에 예시로만 실려 있으며, 실제 화면 문구는 배포 버전에 따라 달라질 수 있습니다. NH농협은행 실제 은행 앱 화면 캡처를 곁들인 단계별 예시는 NH농협은행 대시보드 직접 등록 예시를 참고하세요.

외부 시스템과의 연동은 이 API 매뉴얼을, 인증번호 암호화 키 발급/활성화는 인증번호 암호화 수신 절을 참고하세요.

워크스페이스 관리 API v2

Base URL은 https://api.lunepay.app/api/v2입니다. 모든 v2 경로는 워크스페이스 API 키로 인증하며 URL에 워크스페이스 ID를 받지 않습니다. 키에 연결된 하나의 활성 워크스페이스의 데이터만 조회·등록·관리할 수 있고, 워크스페이스 자체의 생성·조회·수정은 포함하지 않습니다.

입금 내역

GEThttps://api.lunepay.app/api/v2/deposits

입금 목록을 조회합니다. is_matched, bank_account_id, limit, offset 필터를 사용할 수 있습니다.

POSThttps://api.lunepay.app/api/v2/deposits

해당 워크스페이스에 속한 bank_account_id로 수동 입금 내역을 등록합니다.

GEThttps://api.lunepay.app/api/v2/deposits/{deposit_id}

입금 상세를 조회합니다.

주문 목록·상세·관리

GEThttps://api.lunepay.app/api/v2/orders

주문 목록을 조회합니다. status, has_deposit, limit, offset 필터를 사용할 수 있습니다.

GEThttps://api.lunepay.app/api/v2/bank-accounts/{bank_account_number}/orders

계좌번호로 그 계좌 입금에 실제 매칭된 주문만 조회합니다. 계좌 UUID가 아닌 전체 계좌번호를 넣으며 하이픈은 생략할 수 있습니다. status, limit, offset 필터를 사용할 수 있습니다.

예를 들어 우리은행 계좌 1002-123-456789의 주문을 조회하려면 다음처럼 호출합니다. 이 URL에는 계좌 UUID나 워크스페이스 ID를 넣지 않습니다.

BASH
curl -X GET 'https://api.lunepay.app/api/v2/bank-accounts/1002-123-456789/orders?limit=50' \
  -H 'Authorization: Bearer lpay_발급받은_API_키'

성공하면 해당 계좌로 실제 입금되어 매칭 완료된 주문 배열을 반환합니다. 하이픈을 뺀 1002123456789도 같은 계좌로 인식하며, API 키의 워크스페이스에 없는 계좌번호는 404입니다.

POSThttps://api.lunepay.app/api/v2/orders

외부 주문을 생성합니다. v1 간편 주문과 같은 order_id 및 Idempotency-Key 계약을 사용합니다.

GEThttps://api.lunepay.app/api/v2/orders/{order_id}

주문 상세를 조회합니다.

PATCHhttps://api.lunepay.app/api/v2/orders/{order_id}

주문번호, 입금자명, 고객 정보, 메모, 상태를 관리합니다. 취소된 주문은 다시 열 수 없습니다.

페이지네이션: limit과 offset

GET /api/v2/orders, GET /api/v2/deposits, 계좌번호별 주문 조회는 모두 limitoffset을 사용합니다. offset은 페이지 번호가 아니라 앞에서 건너뛸 항목 수이며, 생략하면 limit=50, offset=0입니다. limit은 1~100입니다.

BASH
# 주문: 페이지당 50건
GET /api/v2/orders?limit=50&offset=0    # 1페이지 (1~50번째)
GET /api/v2/orders?limit=50&offset=50   # 2페이지 (51~100번째)
GET /api/v2/orders?limit=50&offset=100  # 3페이지 (101~150번째)

# 입금도 같은 방식
GET /api/v2/deposits?limit=50&offset=0
GET /api/v2/deposits?limit=50&offset=50
GET /api/v2/deposits?limit=50&offset=100

# 특정 계좌 주문도 같은 방식
GET /api/v2/bank-accounts/1002-123-456789/orders?limit=50&offset=50

필터를 함께 사용할 때는 모든 페이지에서 같은 필터를 유지하세요. 예: status=matched&limit=50&offset=50. 반환 배열이 요청한 limit보다 적으면 마지막 페이지입니다.

수동 매칭과 해제

POSThttps://api.lunepay.app/api/v2/orders/{order_id}/match

주문에 하나 이상의 입금을 수동 매칭합니다. body의 deposit_ids는 선택사항입니다.

POSThttps://api.lunepay.app/api/v2/matches/manual

입금 기준으로 주문을 수동 매칭하거나, order_id를 생략해 입금만 수동 처리합니다.

POSThttps://api.lunepay.app/api/v2/matches/{deposit_id}/unmatch

입금의 수동 매칭을 해제합니다.

JSON
{
  "deposit_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8"
}

다른 워크스페이스의 계좌·입금·주문 UUID는 조회하거나 변경할 수 없으며 404로 처리됩니다.

계좌번호별 주문 조회는 해당 계좌로 입금되어 매칭된 주문만 반환합니다. 대기 주문은 현재 특정 계좌에 배정되지 않으므로 이 조회에 포함되지 않습니다.

주문 API

입금 확인이 필요한 주문 정보를 등록하고 관리합니다.

외부 연동 주문 생성 (권장)

POST/orders

워크스페이스 API 키로 재시도 안전한 주문을 생성합니다.

Header필수설명
Authorization: Bearer {api_key}워크스페이스 API 키
Idempotency-Key권장재시도마다 동일하게 보내는 1~200자 고유 키

Request Body

JSON
{
  "order_id": "CHARGE-20260829-0001",
  "amount": 14510,
  "depositor_name": "12345678",
  "memo": "무통장 충전"
}

Idempotency-Key가 같은 동일 요청은 기존 주문과 원래의 201 응답을 반환하며, Idempotent-Replay: true 헤더가 설정됩니다.

같은 키 또는 같은 order_id에 다른 요청 본문을 보내면 409 Conflict가 반환됩니다. 호환성을 위해 헤더가 없는 기존 호출도 order_id 기준으로 중복 생성되지 않지만, 신규 연동은 반드시 헤더를 함께 보내세요.

정확히 8자리 숫자 입금자명은 지원 은행 SMS가 입금자명 위치에 그대로 표시하는 경우 사용할 수 있습니다. 자동 매칭의 정확성을 위해 같은 워크스페이스에서 동시에 대기 중인 주문에는 금액과 입금자명 조합을 고유하게 운영해 주세요.

외부 연동 주문 취소

PATCH/orders/{order_id}

워크스페이스 API 키로 생성한 주문을 취소합니다.

JSON
{
  "status": "cancelled"
}

경로의 order_id에는 주문 생성 응답의 LunePay 주문 UUID(id)를 사용합니다. 외부 서비스가 보낸 요청 본문의 order_id와는 다른 값입니다. 이 공개 엔드포인트는 cancelled 전환만 지원합니다.

주문 목록 조회

GET/workspaces/{id}/orders

사용자 access token과 워크스페이스 조회 권한이 필요합니다.

Query ParameterTypeDescription
statusstringpending, matched, partial, cancelled, expired
limitinteger조회 개수 (기본: 50)
offsetinteger오프셋 (기본: 0)
has_depositboolean매칭된 입금 연결 여부 필터

이 v1 대시보드 경로는 사용자 access token과 멤버 권한을 계속 사용합니다. 워크스페이스 API 키 연동은 v2의 GET /api/v2/orders, GET /api/v2/orders/{order_id}, PATCH /api/v2/orders/{order_id}를 사용하세요. 웹훅 수신자는 안정적인 이벤트 ID로 중복 처리해야 합니다.

대시보드 주문 등록

POST/workspaces/{id}/orders

입금 대기 중인 주문을 생성합니다.

Request Body

JSON
{
  "order_number": "A-2024-0001",
  "amount": 14510,
  "depositor_name": "홍길동",
  "customer_email": "[email protected]",
  "customer_phone": "01012345678",
  "memo": "무통장 입금",
  "expires_at": null
}

Response (201 Created)

JSON
{
  "id": "ed4e387b-1487-41d5-a297-5130e5c638b8",
  "workspace_id": "9d5d720a-cf07-4a82-b262-ef7b34d2cb73",
  "order_number": "A-2024-0001",
  "amount": 14510,
  "depositor_name": "홍길동",
  "status": "pending",
  "customer_email": "[email protected]",
  "customer_phone": "01012345678",
  "memo": "무통장 입금",
  "expires_at": null,
  "matched_at": null,
  "created_at": "2024-02-12T11:00:00+09:00",
  "updated_at": "2024-02-12T11:00:00+09:00"
}

주문 수정

PATCH/workspaces/{id}/orders/{order_id}

주문 정보를 수정하거나 상태를 변경합니다.

JSON
{
  "depositor_name": "김철수",
  "status": "cancelled"
}

웹훅

워크스페이스 설정에서 등록한 URL로 입금 매칭 및 주문 만료 이벤트를 HTTP POST로 전송합니다. 발송 이력과 실패 건 수동 재전송은 대시보드의 웹훅 로그에서 확인할 수 있습니다.

수신 서버는 설정한 웹훅 URL에서 POST 요청을 받아야 합니다. 수신 주소와 워크스페이스 API 키는 대시보드 설정에서 관리합니다.

이벤트와 성공 응답

이벤트설명
deposit.matched입금과 주문의 매칭이 완료됨
order.expired입금 기한이 지나 대기 주문이 취소됨
bank_account.verification.updated계좌 인증 대기열/상태가 바뀔 때(대기열 등록, 순서 도달, SMS 신청 확인, 코드 수신, 완료, 취소, 만료)
요청 헤더값 / 의미
Content-Typeapplication/json; charset=utf-8
X-LunePay-Event-Id본문 id와 같은 UUID 이벤트 ID
X-LunePay-Signature워크스페이스 API 키를 secret으로 한 원본 body HMAC-SHA256 hex

모든 2xx 응답을 성공으로 처리합니다. 응답 본문 형식은 자유이며, 예를 들어 {"ok":true}와 함께 200을 반환하면 됩니다.

deposit.matched 예시

JSON
{
  "id": "b18cf652-0aec-4ec5-8c3a-0ab13b2c6d09",
  "event": "deposit.matched",
  "timestamp": "2026-08-29T12:34:56+09:00",
  "data": {
    "deposit_id": "62f09f41-6dbe-4561-b47c-a6fec1d34d7f",
    "bank_name": "신한은행",
    "bank_account_number": "110-123-456789",
    "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8",
    "order_number": "CHARGE-20260829-0001",
    "amount": "14510",
    "depositor_name": "12345678",
    "matched_at": "2026-08-29T12:34:56+09:00",
    "cash_receipt": null
  }
}

data.bank_namedata.bank_account_number는 각각 입금을 받은 은행명과 계좌번호입니다. 실제 입금 없이 운영자가 주문만 매칭한 match_source: manual_order 이벤트에는 해당 계좌가 없어 이 필드들이 포함되지 않습니다.

order.expired 예시

JSON
{
  "id": "036c263d-3e48-4d5d-bf59-5d7de1dbfc7b",
  "event": "order.expired",
  "timestamp": "2026-08-29T12:44:56+09:00",
  "data": {
    "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8",
    "order_number": "CHARGE-20260829-0001",
    "status": "cancelled",
    "reason": "payment_timeout",
    "expires_at": "2026-08-29T12:44:56+09:00"
  }
}

bank_account.verification.updated 예시

JSON
{
  "id": "8f4b8f2e-7e40-4b0a-9a8a-7a6a2a9b6b31",
  "event": "bank_account.verification.updated",
  "timestamp": "2026-09-01T13:02:30+00:00",
  "data": {
    "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
    "verification_id": "6f0a6e3e-2c88-4c7e-9a0e-9d4a6a0b6b90",
    "bank_code": "088",
    "status": "code_received",
    "phone_number": "010-0000-0000",
    "verification_code": "123456",
    "verification_code_encrypted": null,
    "expires_at": "2026-09-01T13:05:00+00:00",
    "queue_position": 0,
    "estimated_wait_seconds": 0,
    "sequence": 3
  }
}

dataGET /bank-accounts/{bank_account_id}/verification과 같은 상태 객체에 bank_code만 추가된 형태입니다. 위 예시처럼 status가 현재 진행 중인 순서(queue_position: 0)를 가리킬 때는 phone_number도 실제로 사용 중인 수신 번호를 그대로 담아 반환합니다 — null이 아닙니다. verification_code는 워크스페이스가 인증번호 암호화를 켰다면 null이 되고 대신 verification_code_encrypted가 채워집니다. 이 이벤트도 deposit.matched/order.expired와 같은 워크스페이스 webhook_url·서명 방식을 그대로 사용하며, 콜백을 받으려면 기존 웹훅 URL만 등록되어 있으면 됩니다. 폴링만으로 연동한다면 웹훅 URL은 필요하지 않습니다.

최소 한 번 이상 전송(at-least-once)과 재시도 특성상 이벤트가 순서대로 도착하지 않을 수 있습니다. X-LunePay-Event-Id로 중복을 제거하고, 계좌별로 data.sequence가 이전에 처리한 값보다 큰 이벤트만 적용하세요. 시도가 이미 다음 단계로 넘어갔거나 종료된 뒤에는 그 이전 queued/ready 뿐 아니라 pending/code_received 이벤트가 뒤늦게 도착해도 LunePay가 억제하지만, 수신 측에서도 sequence 검사로 한 번 더 방어하는 것을 권장합니다.

검증과 재시도

X-LunePay-Event-Id와 본문의 id는 같은 고유 이벤트 ID이며 재전송과 대시보드 수동 재전송에도 유지됩니다. 수신 서비스는 이 값으로 중복 처리해 주세요. 재시도 시 timestamp는 전송 시각으로 갱신될 수 있습니다.

X-LunePay-Signature는 워크스페이스 API 키를 secret으로 사용해 원본 request body를 HMAC-SHA256으로 계산한 hex 값입니다. body를 다시 직렬화하지 말고 수신한 원문으로 검증해야 합니다.

타임아웃·네트워크 오류·비-2xx 응답에는 30초, 2분, 10분 뒤 재시도합니다. 최초 발송 포함 최대 4회이며, 요청 타임아웃은 10초입니다.

입금 API

은행으로부터 수집된 입금 내역을 조회하거나 수동으로 등록합니다.

입금 내역 조회

GET/workspaces/{id}/deposits
Query ParameterTypeDescription
is_matchedboolean매칭 여부 (true/false)
bank_account_idUUID특정 계좌 필터링
limitinteger조회 개수 (기본: 50)
offsetinteger오프셋 (기본: 0)

수동 입금 등록

POST/workspaces/{id}/deposits

관리자가 수동으로 입금 내역을 등록할 때 사용합니다.

Request Body

JSON
{
  "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "amount": 14510,
  "depositor_name": "홍길동",
  "deposited_at": "2024-02-01T11:05:00+09:00",
  "raw_sms": null
}

Response (201 Created)

JSON
{
  "id": "62f09f41-6dbe-4561-b47c-a6fec1d34d7f",
  "bank_account_id": "0f3757be-2e0a-4f20-b491-4fdddb1dd1d5",
  "amount": 14510,
  "depositor_name": "홍길동",
  "deposited_at": "2024-02-01T11:05:00+09:00",
  "matched_order_id": null,
  "is_matched": false,
  "raw_sms": null,
  "created_at": "2024-02-12T11:05:00+09:00"
}

매칭 API

입금 내역과 주문을 수동으로 연결하거나 해제합니다.

수동 매칭

POST/workspaces/{id}/matches/manual

특정 입금 내역과 주문을 강제로 매칭합니다.

JSON
{
  "deposit_id": "62f09f41-6dbe-4561-b47c-a6fec1d34d7f",
  "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8"
}

Response

JSON
{
  "success": true,
  "message": "매칭 완료: 입금 #62f09f41-6dbe-4561-b47c-a6fec1d34d7f ↔ 주문 #ed4e387b-1487-41d5-a297-5130e5c638b8",
  "deposit_id": "62f09f41-6dbe-4561-b47c-a6fec1d34d7f",
  "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8"
}

매칭 해제

POST/workspaces/{id}/matches/{deposit_id}/unmatch

기존 매칭을 해제하고 주문을 PENDING 상태로 되돌립니다.

JSON
{
  "success": true,
  "message": "매칭 해제 완료: 입금 #62f09f41-6dbe-4561-b47c-a6fec1d34d7f (주문 #ed4e387b-1487-41d5-a297-5130e5c638b8 → PENDING)",
  "deposit_id": "62f09f41-6dbe-4561-b47c-a6fec1d34d7f",
  "order_id": "ed4e387b-1487-41d5-a297-5130e5c638b8"
}

오류 코드

API 요청 시 발생할 수 있는 주요 오류 코드입니다.

Status CodeError TypeDescription
400Bad Request입력값 오류 또는 매칭 조건 불가 (예: 워크스페이스에 해당 계좌가 없음)
401UnauthorizedBearer credential이 없거나 유효하지 않음
403Forbidden워크스페이스 역할에 필요한 권한이 없음
402Payment Required현재 플랜의 생성 또는 사용량 한도를 초과함
409ConflictIdempotency-Key 또는 외부 order_id가 다른 요청 본문과 충돌함
422Validation ErrorUUID, enum, 필수 필드 등 요청 형식이 유효하지 않음
404Not Found요청한 리소스(워크스페이스, 계좌, 주문 등)를 찾을 수 없음