토스페이먼츠 V2 SDK를 Next.js App Router 라우트 핸들러에 붙이는 절차를 주문 생성, 금액 대조, 멱등키 승인, 웹훅 secret 대조와 조회 API 재검증 순서로 정리합니다. 공식 문서 기준(2026-10-17 확인)으로 AI 생성 결제 코드를 검수하는 대조표를 함께 제공합니다.
토스페이먼츠 결제는 서버가 승인 API를 호출해야 확정됩니다. 이 글은 토스페이먼츠 공식 문서(2026-10-17 확인)를 기준으로 주문 생성·금액 대조·승인·웹훅 재검증을 라우트 핸들러 단위로 정리합니다. AI가 생성한 결제 코드의 검수 기준으로도 쓸 수 있습니다.
핵심 요약
- 금액은 주문 생성 때 서버가 저장하고, successUrl의
amount를 그 값과 대조한 뒤에만 승인합니다. - 시크릿 키는 접두어 없는 서버 환경변수에 두고
api.tosspayments.com은 라우트 핸들러에서만 호출합니다. - 승인 요청에는 주문마다 저장해 둔
Idempotency-Key를 붙입니다. 멱등키는 15일간 유효합니다. - 일반 결제 웹훅에는 서명이 없습니다. 본문의
secret을 저장값과 대조하고 조회 API로 상태를 다시 확인합니다. - 가상계좌는
DEPOSIT_CALLBACK으로 입금을 확인한 시점에 완료합니다.
전체 흐름과 파일 구조
브라우저는 결제 인증만 요청하고 승인과 결과 저장은 상점 서버가 시크릿 키로 처리합니다. App Router에서는 아래처럼 파일을 나눕니다.
| 단계 | 파일(예시) | 실행 위치와 키 | AI 생성 코드가 자주 틀리는 것 |
|---|---|---|---|
| 주문 생성 | app/api/orders/route.ts | 서버, 키 없음 | 클라이언트가 보낸 금액을 그대로 저장 |
| 결제창 렌더·요청 | app/checkout/page.tsx ("use client") | 브라우저, 클라이언트 키 | 시크릿 키를 NEXT_PUBLIC_*에 넣음 |
| 승인 | app/api/payments/confirm/route.ts | 서버, 시크릿 키 | 쿼리의 amount를 검증 없이 전달 |
| 웹훅 수신 | app/api/webhooks/toss/route.ts | 서버, 시크릿 키(조회용) | 일반 결제 웹훅에 없는 서명 검증을 지어냄 |
키 분리: 테스트와 라이브, 클라이언트와 시크릿
API 키 문서 기준으로 주문서형 연동 키는 클라이언트 키가 test_gck/live_gck, 시크릿 키가 test_gsk/live_gsk로 시작합니다. g가 없는 test_ck/test_sk는 구버전 결제창·브랜드페이·자동결제용 개별 연동 키입니다. 세트가 다른 키나 테스트·라이브 키를 섞으면 INVALID_API_KEY 오류가 납니다.
환경변수는 NEXT_PUBLIC_TOSS_CLIENT_KEY와 TOSS_SECRET_KEY 두 개로 나눕니다. Next.js 문서는 NEXT_PUBLIC_ 값이 빌드 시점에 브라우저 번들에 인라인된다고 설명합니다. 테스트와 라이브는 같은 변수 이름을 배포 환경별로 다르게 채우고, 운영 서버 기동 시 시크릿 키가 live_로 시작하는지 확인합니다.
주문 생성과 결제창 요청
연동 가이드는 결제 요청 전에 orderId와 amount를 서버에 저장하라고 안내합니다. 주문 생성 라우트는 상품 ID만 받아 서버 가격표로 금액을 계산하고, 승인에 쓸 멱등키(UUID)도 함께 저장합니다. orderId는 영문 대소문자, 숫자, -, _로 이루어진 6~64자 문자열이어야 합니다.
브라우저 쪽은 클라이언트 컴포넌트에서 @tosspayments/tosspayments-sdk 패키지의 loadTossPayments(클라이언트 키)로 초기화하고 widgets({ customerKey }), setAmount({ currency: "KRW", value }), renderPaymentMethods({ selector }), renderAgreement({ selector }) 순서로 호출합니다. 결제 버튼에서는 requestPayment({ orderId, orderName, successUrl, failUrl })를 부릅니다. V2 SDK에서 금액은 객체이고 금액 변경은 setAmount()로 합니다.
customerKey에는 이메일이나 회원 ID처럼 추측 가능한 값을 쓰지 않고 비회원은 ANONYMOUS를 씁니다. requestPayment()의 Promise 방식은 모바일에서 쓸 수 없으므로 리다이렉트 방식을 기본으로 둡니다.
successUrl에서 금액 대조 후 승인
인증이 성공하면 successUrl에 paymentType, orderId, paymentKey, amount 네 값이 쿼리로 붙습니다. 성공 페이지는 이 값을 승인 라우트로 POST합니다. LLM Quick Reference는 브라우저 콘솔에서 바꾼 금액을 상점이 그대로 승인하면 그 금액으로 결제가 완료된다고 명시합니다. 승인 라우트는 저장된 금액과 비교하고 승인 API에도 저장된 금액을 넣습니다. TOSS(https://api.tosspayments.com/v1/payments)와 tossAuth()는 서버 전용 파일 lib/toss.ts에 둡니다.
// app/api/payments/confirm/route.ts
import { TOSS, tossAuth } from "@/lib/toss";
import { getOrder, saveTossResult } from "@/lib/orders";
export async function POST(req: Request) {
const { paymentKey, orderId, amount } = await req.json();
const order = await getOrder(orderId);
if (!order || Number(amount) !== order.amount)
return Response.json({ code: "AMOUNT_MISMATCH" }, { status: 400 });
const res = await fetch(`${TOSS}/confirm`, {
method: "POST",
headers: {
Authorization: tossAuth(),
"Content-Type": "application/json",
"Idempotency-Key": order.idempotencyKey, // 주문 생성 때 저장한 UUID
},
body: JSON.stringify({ paymentKey, orderId, amount: order.amount }),
});
if (res.ok) {
const p = await res.json(); // Payment 객체
await saveTossResult(orderId, { paymentKey, status: p.status, secret: p.secret });
return Response.json({ status: p.status }); // DONE 또는 WAITING_FOR_DEPOSIT
}
// 실패 응답만으로 실패 처리하지 않는다: GET /v1/payments/{paymentKey}로 상태 조회 후 저장
return Response.json({ error: await res.json() }, { status: 409 });
}
인증 헤더는 시크릿 키:를 base64로 인코딩한 Basic 값이며 콜론 누락이 가장 흔한 실수입니다. 권한(구독 등급, 다운로드 권한)은 status가 DONE일 때만 부여합니다. 가상계좌는 승인 응답이 WAITING_FOR_DEPOSIT이므로 이 시점에 완료로 표시하지 않습니다.
멱등키는 최대 300자이고 첫 요청일부터 15일간 유효합니다. 처리 중인 같은 요청을 다시 보내면 409 IDEMPOTENT_REQUEST_PROCESSING이 돌아오며, 에러가 난 요청을 키만 바꿔 재시도하는 것은 위험하다고 문서에 적혀 있습니다. 결제 인증 후 10분 안에 승인 API를 호출하지 않으면 상태가 EXPIRED가 됩니다.
failUrl에는 code, message, orderId가 붙으며 이때는 승인 API를 호출하지 않습니다. 구매자가 취소한 PAY_PROCESS_CANCELED에는 orderId가 없습니다. 실패 페이지는 code별 안내와 주문서로 돌아가는 경로를 둡니다.
웹훅: 서명 대신 secret 대조와 조회 API
웹훅은 개발자센터에서 상점아이디(MID)별로 등록합니다. 서명 헤더 tosspayments-webhook-signature는 지급대행 이벤트에만 붙습니다.
| 이벤트 | 본문의 식별자 | 검증 방법 | 완료 처리 기준 |
|---|---|---|---|
PAYMENT_STATUS_CHANGED | data(Payment 객체) | 저장한 secret 대조 후 GET /v1/payments/{paymentKey} | 조회 결과 status |
DEPOSIT_CALLBACK | orderId, secret (paymentKey 없음) | 저장한 secret 대조 후 GET /v1/payments/orders/{orderId} | 조회 결과 DONE |
payout.changed, seller.changed | 지급대행 객체 | 서명 헤더 HMAC SHA-256 | 지급대행 전용 |
API 레퍼런스는 Payment 객체의 secret이 웹훅 본문의 secret과 같으면 정상 웹훅이라고 설명합니다. 두 이벤트를 모두 등록하면 가상계좌 웹훅이 두 번 오므로 처리는 멱등해야 합니다. 자동결제와 키인 결제는 완료 웹훅을 보내지 않습니다.
// app/api/webhooks/toss/route.ts
import { TOSS, tossAuth } from "@/lib/toss";
import { getOrder, saveTossResult } from "@/lib/orders";
export async function POST(req: Request) {
const body = JSON.parse(await req.text());
const p0 = body.eventType === "PAYMENT_STATUS_CHANGED" ? body.data : body;
const order = p0?.orderId ? await getOrder(p0.orderId) : null;
if (!order) return new Response("ignored", { status: 200 });
if (order.tossSecret && p0.secret !== order.tossSecret)
return new Response("secret mismatch", { status: 400 });
const url = p0.paymentKey
? `${TOSS}/${encodeURIComponent(p0.paymentKey)}`
: `${TOSS}/orders/${encodeURIComponent(p0.orderId)}`;
const res = await fetch(url, { headers: { Authorization: tossAuth() } });
if (!res.ok) return new Response("retry", { status: 500 }); // 재전송 유도
const p = await res.json();
const status = p.totalAmount === order.amount ? p.status : "AMOUNT_MISMATCH";
await saveTossResult(order.orderId, { paymentKey: p.paymentKey, status, secret: p.secret });
return new Response("ok", { status: 200 });
}
본문은 Next.js 웹훅 예제처럼 request.text()로 받습니다. 웹훅 가이드는 10초 안에 200 응답을 요구하고, 실패하면 1분부터 4096분까지 간격을 늘려 최대 7회(3일 19시간) 재전송합니다.
조회가 느린 구조라면 큐에 넣고 먼저 200을 응답합니다. 금액이 다르면 AMOUNT_MISMATCH로 저장해 사람이 확인합니다.
라이브 전환 전 점검과 비용
배포 체크리스트는 라이브 키·시크릿 키 노출·API 버전(최신 2022-11-16)·카드사와 통신사 심사·결제수단별 테스트·취소와 부분 취소를 점검하게 합니다. 테스트 환경 API는 분당 100건으로 제한되고 카카오페이는 계약 후 상점 테스트 키로만, 페이코는 라이브 키로만 테스트됩니다. 가상계좌는 문서용 테스트 키로 테스트할 수 없습니다. 에러 재현에는 TossPayments-Test-Code 헤더를 씁니다.
| 항목(토스페이먼츠 요금 안내) | 금액 |
|---|---|
| 가입비 | 220,000원 (최초 1회) |
| 연관리비 | 110,000원 (연 1회) |
| 신용·체크카드 일반, 간편결제 | 3.4% |
| 가상계좌 | 건당 400원 |
| 계좌이체 | 2.0% (최저 건당 200원) |
요금 페이지(기준일 표기 없음, 2026-10-17 확인)는 부가세 10% 별도·일부 간편결제 추가 수수료·계약 형태별 가입비와 연관리비 차이를 함께 적고 있습니다. 실제 요율은 계약 시 확인합니다.
AI 도구로 연동할 때
토스페이먼츠는 MCP 서버와 llms.txt, LLM Quick Reference를 제공합니다. Claude Code 등록 명령은 claude mcp add tosspayments-integration-guide -- npx -y @tosspayments/integration-guide-mcp@latest입니다. 생성된 코드는 Quick Reference §8 오류 패턴 표로 사람이 확인합니다. 결제 구조 설계는 서비스 구축 문의하기로 요청할 수 있습니다.
함께 읽을 글
- 바이브코딩 결제 연동 가이드: 국내 PG·해외 MoR 선택 기준
- AI가 짠 결제 코드 점검표: 금액 위변조·시크릿 키·웹훅 검증 8가지
- 포트원 연동 가이드: V2 웹훅 서명 검증과 PG 계약 순서
- 결제 붙이기 전 법적 준비: 사업자등록·통신판매업 신고·환불 고지 체크리스트
- 코텍시스 서비스 구축 사업부
자주 묻는 질문
사업자등록 없이 테스트 키를 받을 수 있나요?
API 키 문서에 따르면 회원가입은 이메일만으로 할 수 있고, 전자결제 신청 전에는 체험 상점의 일부 테스트 키와 문서용 테스트 키로 연동할 수 있습니다. 내 상점 키와 라이브 키는 전자결제 신청 이후에 나옵니다. 신청 요건은 토스페이먼츠 고객센터(1544-7772)에 확인합니다.
웹훅 서명 검증은 어떻게 하나요?
일반 결제 웹훅에는 서명 헤더가 없습니다. 저장한 secret과 본문의 secret을 비교하고 조회 API로 상태를 확인합니다. HMAC 서명 검증은 지급대행 웹훅에만 적용합니다.
승인 API가 실패하면 결제도 실패한 것인가요?
단정하지 않습니다. Quick Reference는 승인 실패를 즉시 결제 실패로 처리하는 것을 오류 패턴으로 꼽고, 조회 API로 상태를 확인한 뒤 분기하라고 안내합니다.
MCP를 붙이면 무엇이 달라지나요?
AI 도구가 공식 문서를 직접 검색하므로 구버전 SDK 메서드나 다른 PG의 필드명이 섞일 여지가 줄어듭니다. 금액 검증과 키 관리는 상점 코드의 책임으로 남습니다.
출처
토스페이먼츠 공식 문서, 모두 2026-10-17 접속.
- LLM Quick Reference, https://docs.tosspayments.com/guides/v2/get-started/llms-quick-reference
- 주문서형 결제 연동하기, https://docs.tosspayments.com/guides/v2/payment-widget/integration
- 주문서형 결제 SDK, https://docs.tosspayments.com/sdk/v2/js/payment-widget
- API 키, https://docs.tosspayments.com/reference/using-api/api-keys
- 인증 및 기타 헤더 설정(멱등키), https://docs.tosspayments.com/reference/using-api/authorization
- 웹훅 연결하기, https://docs.tosspayments.com/guides/v2/webhook
- 웹훅 이벤트, https://docs.tosspayments.com/reference/using-api/webhook-events
- 코어 API(Payment 객체), https://docs.tosspayments.com/reference
- 환경 설정하기, https://docs.tosspayments.com/guides/v2/get-started/environment
- 배포 체크리스트, https://docs.tosspayments.com/guides/v2/deploy-checklist
- AI 도구 연동 가이드, https://docs.tosspayments.com/guides/v2/get-started/llms-guide
- 요금 안내, https://www.tosspayments.com/about/fee
- Next.js 16.3 문서, Route Handlers 및 route.js 레퍼런스(Webhooks), Environment Variables
코텍시스 창업자로 AX 컨설팅과 기업 AI 교육, AI 에이전트 개발을 직접 수행합니다.
코텍시스 AI 인사이트 최신 논문 리뷰
토큰당 KV 캐시 크기로 계산하는 온프레미스 동시 처리량
vLLM 논문이 보인 것처럼 LLM 서빙의 동시 처리량은 GPU 메모리에서 가중치를 빼고 남은 공간을 토큰당 KV 캐시 크기로 나눈 값이 정합니다. Qwen3-8B와 Qwen3-32B의 config.json으로 계산하면 80GiB GPU 1장에서 8,192토큰 세션 상한이 57개와 9개로, 가중치 크기 차이 4배보다 큰 6배 이상의 차이가 납니다.
초안 모델로 생성 속도를 높이는 추측 디코딩
Google Research의 추측 디코딩은 작은 초안 모델이 제안한 토큰을 큰 모델이 한 번에 검증하고 확률적 수락 규칙으로 출력 분포를 그대로 유지해, T5-XXL의 생성 속도를 재학습 없이 2배에서 3배 높였습니다. M3 Pro 맥북의 Gemma 4 12B 단일 사용자 실측에서는 초안을 켜면 같은 출력을 유지한 채 생성 속도가 15.3 tok/s에서 33.4 tok/s로 2.18배 올랐습니다.
AWQ 양자화: 활성값을 보고 가중치를 지키는 4비트 양자화
MIT 연구진의 AWQ는 활성값 분포를 기준으로 중요한 가중치 1%만 보호해도 저비트 양자화 오류가 크게 줄어든다는 관찰에서 출발해 재학습 없는 4비트 양자화를 구현했고 전용 커널 TinyChat으로 FP16 대비 3배 이상의 추론 속도를 달성했습니다. 온프레미스 sLLM을 소수의 GPU로 서빙해야 하는 기업에게 정확도 손실 없이 메모리와 비용을 동시에 줄이는 사실상의 표준 기법입니다.

