코어 API

온누리PG 결제 플랫폼의 표준 REST 사양과 운영 모범 사례를 한 번에 확인하세요.

개요

온누리PG 코어 API는 대규모 트래픽과 금융 규제를 고려해 설계된 서버-서버 REST 인터페이스입니다. SECURE STABLE

모든 요청은 TLS 1.3 기반 암호화 통신을 전제로 하며, 가용성과 무결성을 위해 멀티 리전 액티브-액티브 구조로 운영됩니다. SLA, 버전 정책, 가이드라인은 아래 세부 문서에서 연계해 제공합니다.

  • Base URL: https://api.onnuripg.com (지역별 엔드포인트는 추후 공지)
  • 인증: Authorization: Basic BASE64(SECRET_KEY:) — 클라이언트 키 사용 금지 IMPORTANT
  • 콘텐츠 타입: application/json
  • 타임존: 모든 날짜·시간은 Asia/Seoul (KST) 기준 ISO8601 포맷으로 제공됩니다.
  • 버전 관리: X-ONNURI-API-VERSION 헤더를 지정해 안정적으로 버전 고정 가능합니다.
  • Rate Limit: 기본 1,000 RPS, burst 3초 창 기준. 초과 시 429 응답과 Retry-After 헤더가 반환됩니다.

빠른 시작

  • 시크릿 키(sk_test_/sk_live_)는 서버 전용 환경 변수로 격리합니다. SECURE
  • 최종 결제 승인은 반드시 서버 사이드에서 수행해 위변조를 차단합니다. IMPORTANT
  • 성공·실패 콜백과 웹훅 서명을 검증해 주문 상태를 동기화합니다.

결제 승인 (cURL)


curl --request POST   --url https://api.onnurip.com/v1/payments/confirm   --header 'Authorization: Basic {BASE64_ENCODED_SECRET_KEY}'   --header 'Content-Type: application/json'   --data '{ 
    "paymentKey":"5EnNZRJGvaB...",
    "orderId":"a4CWyWY5m89P...",
	    "amount":1000
	  }'

운영 베스트 프랙티스

  • 결제 승인 전 주문 금액을 DB 혹은 오더 서비스에서 재계산해 위·변조 여부를 확인합니다.
  • 장애 대비를 위해 승인/취소 시 requestId 등 멱등 키를 사용해 재시도 시 중복 처리를 방지하세요.
  • 모든 API 오류 응답은 JSON 포맷을 따르므로, code·message·retryable 필드를 로깅해 운영 지표로 활용하세요.

엔드포인트 요약

  • POST POST /v1/payments/confirm — 서버 금액 검증과 주문 상태 동기화 후 결제 확정
  • GET GET /v1/payments/{paymentKey} — 단건 결제의 전 수명주기 상세 조회
  • GET GET /v1/payments/orders/{orderId} — 주문 기준 다중 결제 이력 조회
  • POST POST /v1/payments/{paymentKey}/cancel — 취소 사유와 함께 결제 금액 환급 처리

Payment 객체

온누리PG가 반환하는 결제 표준 스키마입니다. 결제 수단, 상태, 영수증 정보가 일관된 키로 제공되며 수단별로 하위 객체 구조가 확장됩니다.

주요 필드는 결제 추적과 정산 자동화를 위해 paymentKey, orderId, status, method, approvedAt 순으로 파싱하는 것을 권장합니다. 필드별 상세 정의는 API 메뉴의 하위 문서에서 확인하세요.

응답 예시


{
  "version": "2024-08-01",
  "paymentKey": "pay_123...",
  "type": "NORMAL",
  "orderId": "a4CWyWY5...",
  "orderName": "프리미엄 구독",
  "currency": "KRW",
  "method": "CARD",
  "totalAmount": 1000,
  "balanceAmount": 0,
  "status": "DONE",
  "requestedAt": "2024-09-01T12:01:02+09:00",
  "approvedAt": "2024-09-01T12:01:05+09:00",
  "card": { "issuer": "KAKAOBANK", "number": "1111-****-****-2222" },
  "receipt": { "url": "https://receipts.onnuripg.com/..." }
}

상세 예제

결제 승인

POST /v1/payments/confirm

서버에서 paymentKey·orderId·amount를 교차 검증해 주문 위변조를 차단한 뒤 결제를 확정합니다. IMPORTANT

결제 승인 후에는 주문 시스템에 승인 결과를 기록하고, 정산 시스템과 ERP에 필요한 메타데이터를 비동기로 전달하는 것을 권장합니다.

Request Body

  • paymentKey string — 결제 키
  • orderId string — 주문번호
  • amount number — 결제 금액
curl --request POST --url https://api.onnuripg.com/v1/payments/confirm --header 'Authorization: Basic {ONNURI_SECRET_KEY}' --header 'Content-Type: application/json' --data '{"paymentKey":"5EnNZRJGvaB...","orderId":"a4CWyWY5m89P...","amount":1000}'

paymentKey로 결제 조회

GET /v1/payments/{paymentKey}

가장 최근 결제 상태와 승인·취소 이력을 신속하게 확인할 때 사용합니다.

운영에서 CS가 특정 결제 건을 식별할 때 paymentKey를 기준으로 조회하면 처리 속도가 빨라집니다.

curl --request GET --url https://api.onnuripg.com/v1/payments/5EnNZRJGvaB... --header 'Authorization: Basic {ONNURI_SECRET_KEY}'

orderId로 결제 조회

GET /v1/payments/orders/{orderId}

여러 결제 시도가 포함된 주문 단위를 기준으로 상태를 정합성 있게 조회합니다.

부분 취소나 재승인 시나리오에서는 orderId 기반 조회를 통해 전체 수명주기를 분석한 뒤 후속 처리를 결정하세요.

curl --request GET --url https://api.onnuripg.com/v1/payments/orders/a4CWyWY5m89P... --header 'Authorization: Basic {ONNURI_SECRET_KEY}'

결제 취소

POST /v1/payments/{paymentKey}/cancel

부분 취소를 포함한 금액 조정 시 cancelReason에 상세 사유를 남겨 회계 처리에 활용합니다.

취소 요청은 승인 처리와 동일하게 멱등 요청으로 전송하면 운영 중 네트워크 재전송이 발생해도 안전합니다.

Request Body

  • cancelReason string — 취소 사유
curl --request POST --url https://api.onnuripg.com/v1/payments/5EnNZRJGvaB.../cancel --header 'Authorization: Basic {ONNURI_SECRET_KEY}' --header 'Content-Type: application/json' --data '{"cancelReason":"구매자 변심"}'

웹훅 가이드

가상계좌 입금, 상태 전이, 자동결제 결과 등 비동기 이벤트는 실시간 웹훅으로 전달됩니다. SECURE

웹훅은 최소 두 번 이상 재시도 되므로 멱등 처리를 전제로 구현해야 하며, 네트워크 오류 시 온누리PG는 지정한 페이스로 24시간 동안 재시도합니다.

  • 개발자센터에서 웹훅 URL과 보안 비밀(WEBHOOK_SECRET)을 등록해 서명 검증 기준을 정의합니다.
  • 서버에서 서명 헤더와 원문 바디를 비교해 무결성을 검증합니다. SECURE
  • Idempotency 키를 기준으로 중복 이벤트를 안전하게 무시하거나 재처리합니다. IMPORTANT

검증 스니펫 (Node.js/Express)

HMAC 서명 검증

import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.json({ type: 'application/json' }));

function verifySignature(rawBody, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(signature));
}

app.post('/webhooks/onnuripg', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.header('X-Onnuri-Signature');
  const valid = verifySignature(req.body, signature || '', process.env.WEBHOOK_SECRET || '');
  if (!valid) return res.status(401).end();

  const event = JSON.parse(req.body.toString());
  // TODO: 이벤트 타입별 처리 (payment.confirmed, payment.canceled 등)
  res.status(200).end();
});

app.listen(3000);

NestJS

HMAC 검증 (NestJS)

import { Controller, Headers, Post, Req, Res } from '@nestjs/common';
import * as crypto from 'crypto';

@Controller('webhooks')
export class WebhookController {
  @Post('onnuripg')
  handle(@Req() req: any, @Res() res: any, @Headers('x-onnuri-signature') signature: string) {
    const secret = process.env.WEBHOOK_SECRET || '';
    const body = req.rawBody ?? req.bodyRaw ?? Buffer.from(JSON.stringify(req.body));
    const hmac = crypto.createHmac('sha256', secret).update(body).digest('hex');
    const valid = crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(signature || ''));
    if (!valid) return res.status(401).send('invalid');
    const event = JSON.parse(body.toString());
    // TODO: 이벤트 처리
    return res.status(200).send('ok');
  }
}

Spring Boot (Java)

HMAC 검증 (Spring)

@RestController
@RequestMapping("/webhooks")
public class WebhookController {
  @PostMapping("/onnuripg")
  public ResponseEntity<String> handle(@RequestBody byte[] rawBody, @RequestHeader("X-Onnuri-Signature") String sig) throws Exception {
    String secret = System.getenv("WEBHOOK_SECRET");
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    String h = DatatypeConverter.printHexBinary(mac.doFinal(rawBody)).toLowerCase();
    if (!MessageDigest.isEqual(h.getBytes(), sig.getBytes())) return ResponseEntity.status(401).build();
    // TODO: 이벤트 처리
    return ResponseEntity.ok("ok");
  }
}

Python (FastAPI)

HMAC 검증 (FastAPI)

from fastapi import FastAPI, Request, Header, HTTPException
import hmac, hashlib, os

app = FastAPI()

	@app.post("/webhooks/onnuripg")
	async def handle(request: Request, x_onnuri_signature: str | None = Header(default=None)):
	    body = await request.body()
	    secret = os.getenv("WEBHOOK_SECRET", "")
	    digest = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
	    if not x_onnuri_signature or not hmac.compare_digest(digest, x_onnuri_signature):
	        raise HTTPException(status_code=401)
	    # TODO: 이벤트 처리
	    return {"ok": True}

서명 헤더는 기본적으로 X-Onnuri-Signature이며, 추후 다중 클라이언트 키 시나리오에서는 X-Onnuri-Client로 분기할 수 있습니다.

이벤트 타입

주요 이벤트 이름과 의미를 한눈에 확인하세요.

이벤트설명
payment.confirmed결제가 최종 승인됨
payment.canceled결제가 전액 취소됨
payment.partially_canceled결제가 부분 취소됨
virtual_account.issued가상계좌가 발급됨
virtual_account.deposit.completed가상계좌 입금 완료
billing.key.issued자동결제용 빌링키 발급
payout.requested지급대행 요청 생성
payout.completed지급대행 처리 완료
refund.completed환불 처리 완료

운영 콘솔에서 발급되는 헤더 이름과 서명 알고리즘은 계정 설정에 따라 달라질 수 있습니다. 실제 구성 값을 기준으로 검증 로직을 조정하세요.

라이브 전 체크리스트

  • 서버에서 주문 금액과 상태를 재검증한 뒤 확정 응답을 전송합니다. IMPORTANT
  • 웹훅 서명과 토큰 유효성을 검증해 이벤트 위변조를 차단합니다. SECURE
  • 성공·실패·부분취소·환불 전 경로를 자동화 테스트로 검증합니다.

라이브 전 마지막 단계에서는 콜백 URL, 웹훅 IP 화이트리스트, 결제 수단별 한도 설정을 반드시 재점검하세요.