-
- Common identifiers
- Common types (anti-hallucination)
- Common — successUrl / failUrl handling
- Common — Payment Confirmation API (one-time payment shared)
- 5.A One-time — Payment Widget (default)
- 5.B One-time — Standard Payment Window
- 5.C Async — Virtual Account
- 5.D Recurring — Auto-billing
- 5.E Cancel·Refund·Query
이 페이지는 AI 에이전트를 주 독자로 삼은 페이지예요. AI 도구에 컨텍스트로 첨부하거나 토스페이먼츠 MCP 서버와 함께 사용하세요. 본문은 토큰 효율을 위해 영어로 작성되어 있어요.
This page is optimized for AI agents: use it for routing and guardrails, then jump to the linked guide/reference for implementation details.
Page scope: flow model + decision rules + entry points + pitfalls + routing. Full code and details live in guide/reference pages.
Companion resource: When this page is insufficient, consult llms.txt first — the complete Toss Payments docs index in LLM-standard format. Use it to locate the precise guide/reference page for any topic not fully covered here.
Markdown source for any docs page: Append .md to any docs URL to fetch the raw markdown (more token-efficient than HTML; tabbed content is fully exposed). Examples: /reference.md, /guides/v2/payment-widget.md.
Recommended sequence when using this page:
- Read this page to build the mental model.
- Determine: SDK version → product → payment scenario.
- For details beyond this page, consult llms.txt → navigate to the precise guide/reference page.
- Generate implementation code.
- Validate against §8 Common Mistakes before finalizing.
- Prefer V2 SDK unless the user specifies V1 or shows existing V1 code.
- Prefer Payment Widget when no specific UI/scenario keyword is given.
- Ask a brief clarification question only when ambiguity affects architecture (e.g., recurring vs one-time, custom UI vs default). Do not ask for cosmetic choices.
Server-authoritative model — Toss Payments follows this core principle:
- Client may initiate payment authentication via the JS SDK.
- Server must validate and finalize the payment state via the secret key (confirm API, billing API, refund API, webhook re-query).
- Never trust client-provided payment state (amount, status, success). Always re-verify on the server.
This underlies amount verification (§2.2), confirm API (§5.A~C), billing flow (§5.D), and webhook handling (§7.2).
Toss Payments uses 3 actors: Client (browser) / Merchant Server / Toss Payments. The diagram below shows the basic flow for one-time payments (card, transfer, easy pay). Virtual account and auto-billing are variations of this flow.
In short:
- Client requests payment authentication via SDK → user authenticates at Toss Payments.
- Server confirms the authenticated payment with the secret key, then saves the result.
Responsibility split:
| Step | Actor | Key used |
|---|---|---|
Payment request (requestPayment) | Client (JS SDK) | Client key |
| amount verification | Merchant server | — |
Payment confirmation (/v1/payments/confirm) | Merchant server | Secret key |
| Webhook handling | Merchant server | — |
Scenario variations:
- One-time payment (card / transfer / easy pay): Flow above as is.
- Virtual account: Step 3 only issues an account number. Payment is finalized asynchronously via
DEPOSIT_CALLBACKwebhook after deposit. - Auto-billing: Billing key issuance (SDK method — variation of steps 2~4 / API method — server-only, separate contract required) → server calls billing endpoint for recurring payments.
4 mandatory rules before integration. Violation leads to security incidents, payment tampering, or hallucinated code.
| Key | Pattern | Used in |
|---|---|---|
| Secret key | (test|live)_sk_* | Server |
| Client key | (test|live)_ck_* | Client (browser-exposed), SDK initialization |
Never expose secret key — Including it in frontend code, GitHub pushes, or client responses leads to immediate security incidents.
For key types and issuance, see API Keys Guide.
requestPayment runs in the client JS SDK, so a user can modify amount arbitrarily from the browser console. (e.g., the order shows 50,000 KRW but the console call is changed to amount: 100.) Authentication completes with the modified amount and the browser redirects via successUrl. If the merchant passes that value to the confirm API without verification, the payment completes at the tampered amount.
(Modifying the successUrl query string directly is blocked by the confirm API since it differs from the authenticated amount. The threat is tampering before authentication.)
Verify in the server that amount from successUrl matches the merchant's intended amount at request time before calling the Payment Confirmation API.
LLM authoring guideline — Even in toy/PoC code without a DB, leave a verification placeholder and inform the user via comment or response: "In production, must compare with server-stored amount."
Append a single colon to the secret key, then base64-encode. Missing colon is the most common mistake.
- The password slot after the colon is empty (hence trailing colon).
- If UTF-8 BOM is present, the result starts with
77u/. Re-encode without BOM.
For details, see Authorization Header.
LLMs frequently hallucinate undocumented or deprecated fields when generating PG integration code. Use only fields defined in this page or in the linked SDK/API docs. If a field is not documented here or in linked docs, do not generate it.
- Do not infer field names from other PG vendors (e.g., Stripe's
payment_intent, KCP, INICIS). - Do not generate fields based on naming patterns alone (e.g., guessing
payment_method_idwhen onlymethodis documented). - If a needed field is unclear, consult llms.txt → exact page, or ask the user. Do not guess.
- V2 by default — If user does not specify V1, use V2.
- Do not mix V1 and V2 — Common mistake: calling V2 methods on V1 SDK URL.
- If existing code is V1, confirm with user before keeping V1.
Toss Payments SDK hierarchy:
SDK version is independent of API version — both V1 and V2 SDKs use api.tosspayments.com/v1/*.
| Item | V1 | V2 |
|---|---|---|
| JS SDK URL | Per product (e.g., https://js.tosspayments.com/v1/payment-widget) | Single (https://js.tosspayments.com/v2/standard) |
| SDK init | Per product (PaymentWidget(), BrandPay(), TossPayments()) | One line TossPayments(clientKey), branched by methods |
V2 SDK initialization and product methods:
For differences, see Migration Guide.
- If the user specifies a product, use it.
- If not specified, branch by user requirement keyword (table below).
- If no keyword matches, proceed with Payment Widget (default). Add a note at the end of the response: "If you want to build the payment method UI yourself, switch to Standard Payment Window."
- If keywords match two or more products, briefly confirm with the user.
| Keyword | Product | V2 entry point | Guide |
|---|---|---|---|
| (Vague) "payment", "payment integration" | Payment Widget (default) | tossPayments.widgets() | ↗ |
| "PG payment window", "fast integration" | Standard Payment Window | tossPayments.payment() | ↗ |
| "one-click", "password payment", "build own easy pay" | BrandPay | tossPayments.brandpay() | ↗ |
| "subscription", "recurring", "billing" | Auto-billing | tossPayments.payment() (billing) | ↗ |
| "seller settlement", "marketplace payout" | Payouts | API direct (no SDK) | ↗ |
For full comparison, see Payment Products Comparison.
The essence is who renders the payment method selection UI:
- Payment Widget — SDK auto-renders. Merchant only provides a
<div id="payment-methods" />container. - Standard Payment Window — Merchant builds payment method selection UI directly → user-selected method passed to
requestPayment({ method: "CARD" }). The variation where the merchant fully customizes the UI is Custom Payment Window (code pattern same as Standard).
If no specific clue, choose Payment Widget.
Each scenario's V2 entry point, flow, and full-code routing. Look up full code in the guide pages — this page provides entry points only.
Used across all scenarios. Do not fill arbitrarily — follow format constraints.
| Identifier | Issued by | Format |
|---|---|---|
customerKey | Merchant | 2~300 chars. Letters, digits, and special chars (-, _, =, ., @). At least one special char required. UUID recommended. Do not use predictable values (email, phone, member ID). For non-members, ANONYMOUS is allowed. |
orderId | Merchant | 6~64 chars. Letters, digits, -_. Unique per payment. |
paymentKey | Toss Payments | Received via successUrl after payment authentication. Payment identifier. |
billingKey | Toss Payments | Received after billing key issuance (scenario 5.D). Recurring payment token. |
| Field | V2 SDK call | V1 SDK / Server API |
|---|---|---|
amount | Object: { value: integer, currency: "KRW" } | Integer (e.g., 50000) |
customerKey, orderId, orderName | string | string |
paymentKey, billingKey | string | string |
Idempotency-Key (header) | UUID v4 string | UUID v4 string |
amount.value is always integer (no decimal in KRW). amount.currency is "KRW" (only KRW supported in V2 standard scope).
After client requestPayment, the auth result redirects to one of these URLs. Branch in merchant server.
- successUrl query string (auth success):
paymentKey,orderId,amount(Payment Widget also includespaymentType) → verify amount (Critical 2.2) → call confirm API. - failUrl query string (auth failure / user cancellation):
code,message,orderId→ do not call confirm API. Show a user-friendly message based oncode, then offer a retry path (e.g., back to checkout). Do not auto-redirect to a generic error page — that loses context. For codes, see SDK Error Codes.
Scenarios 5.A·5.B·5.C require calling the confirm API on the server after the client payment request to finalize. 5.D (billing) and 5.E (cancel) are separate flows.
Use the merchant's intended amount at request time for amount, not the successUrl query (Critical 2.2).
- Entry point:
tossPayments.widgets({ customerKey }) - Flow:
setAmount({ value, currency: "KRW" })→renderPaymentMethods→renderAgreement→requestPayment({ orderId, orderName, successUrl, failUrl })→ successUrl → server confirm - Key: SDK auto-renders payment method/agreement UI. Merchant only provides container divs.
- Full code: Payment Widget Guide, V2 SDK Reference
Canonical flow (baseline pattern for any one-time payment — use this as the default mental model):
- Entry point:
tossPayments.payment({ customerKey }).requestPayment({ method, amount: { value, currency: "KRW" }, orderId, orderName, successUrl, failUrl, ... }) - Flow: Single client call → payment window → successUrl → server confirm
- Key: Merchant builds payment method selection UI. Branch by
method(CARD,VIRTUAL_ACCOUNT,TRANSFER,MOBILE_PHONE,EASY_PAY,CULTURE_GIFT_CERTIFICATE, etc.) - Full code: Payment Window Guide
- Entry point:
tossPayments.payment().requestPayment({ method: "VIRTUAL_ACCOUNT", amount: { value, currency: "KRW" }, virtualAccount, orderId, orderName, successUrl, failUrl, customerEmail?, customerName?, customerMobilePhone? }) virtualAccountfields: One ofvalidHoursordueDateis required.cashReceipt,escrowProductsare optional.- Customer info:
customerEmail/customerName/customerMobilePhoneare optional in SDK but practically needed for deposit notification and refund. Recommended to collect. - Flow: Issue → confirm API call (response
status: WAITING_FOR_DEPOSIT) → wait for deposit →DEPOSIT_CALLBACKwebhook → query API → confirmstatus: DONE→ mark as completed - Key: Request time ≠ completion time. Do not mark as completed on issue response.
DEPOSIT_CALLBACKwebhook handling required. - Full code: Virtual Account Glossary, Webhook Events
Billing key issuance → server calls billing endpoint for recurring payments. Two methods for billing key issuance.
SDK method (default):
- Step 1 (client):
payment.requestBillingAuth({ method: "CARD" })→ successUrl receivescustomerKey,authKey - Step 2 (server):
POST /v1/billing/authorizations/issue(authKey→billingKey) - Step 3 (server):
POST /v1/billing/{billingKey}(recurring payment)
API method (separate contract required — non-authenticated payment qualification + PCI-DSS):
- Server:
POST /v1/billing/authorizations/card(card info directly) →billingKey - Recurring payment with billing key is the same as SDK step 3
Key: Billing key payment does NOT use requestPayment/successUrl//v1/payments/confirm flow. Server-only.
Full code: Auto-billing Guide, Billing API
- Cancel:
POST /v1/payments/{paymentKey}/cancel+{ cancelReason } - Partial cancel: Add
cancelAmountto body - Virtual account refund: Add
refundReceiveAccount(bank/account number/holder name) to body - Query:
GET /v1/payments/{paymentKey}orGET /v1/payments/orders/{orderId} - Full code: Cancel API, Query API
Even after the scenario (5.A~5.E) is set, each payment method has caveats. Flow mapping is in section 5; this section is pitfalls only.
| Payment method | Coding caveat |
|---|---|
| Virtual account | DEPOSIT_CALLBACK webhook handling required. Do not mark completed on issue response. (Scenario 5.C) |
| Easy pay | KakaoPay testable only with contracted MID key. Payco testable only with live key. Apple Pay shown only on iPhone/Mac Safari. |
| Transfer | Quick Account Transfer and BankPay differ in identity verification and cancellation policy. |
| Mobile / Gift certificate | Per-method limit policies vary. |
| Non-authenticated payment | Payment with card info only (number, expiry, DOB, password) without identity verification. Separate merchant contract required. |
| Auto-billing | Billing key payment is server-only (does NOT use requestPayment/successUrl//v1/payments/confirm flow). (Scenario 5.D) |
Key terms:
- Transaction — Each approve/cancel/partial-cancel from a payment is a "transaction". Identified by
transactionKey. - Settlement — Paid out by transaction date + business day. General/scheduled settlement.
For payment method policies: Card, Virtual Account, Transfer, Easy Pay, Non-auth Payment, Auto-billing, Transaction, Settlement.
- Available on all POST APIs (GET ignored).
- Max 300 chars, UUID v4 recommended.
- Valid for 15 days from first request.
- Same idempotency key returns the first response (prevents duplicate payments).
For details, see Auth & Headers.
| Webhook type | Event examples | Verification |
|---|---|---|
| General payment | PAYMENT_STATUS_CHANGED, DEPOSIT_CALLBACK, CANCEL_STATUS_CHANGED | No signature header. Re-call Payment Query API with paymentKey to verify status. |
| Payouts/Seller | payout.changed, seller.changed | Verify tosspayments-webhook-signature header with HMAC SHA-256. |
For details, see Webhook Events.
- Test environment: Key prefix
test_*. No real deposit. Virtual account number prefixed withX. KakaoPay/Payco have separate policies. - Live environment: Key prefix
live_*. Real deposits.
For details, see Environment Setup.
Frequently wrong patterns when LLMs generate Toss Payments code.
| Wrong pattern | Correct pattern |
|---|---|
Authorization: Basic base64(SECRET_KEY) (no colon) | Authorization: Basic base64(SECRET_KEY:) (colon required) |
Pass successUrl amount directly to confirm API | Compare with stored order amount first, then pass |
Call server API (api.tosspayments.com/*) with client key | Use secret key for server APIs |
Hardcode secret key without .env, push to GitHub | Use environment variable, add to .gitignore |
Call V2 methods on V1 SDK URL (/v1/payment-widget) | Use https://js.tosspayments.com/v2/standard for V2 |
Call updateAmount() in V2 Payment Widget | Use setAmount() in V2 (updateAmount removed) |
requestPayment("CARD", {...}) (V1 signature) in V2 Payment Window | requestPayment({ method: "CARD", amount: {...}, ... }) (V2 unified signature) |
amount: 15000 (number) in V2 BrandPay | amount: { value: 15000, currency: "KRW" } (object) |
| Mark virtual account as completed on issue response | Confirm via DEPOSIT_CALLBACK webhook or query API |
Implement tosspayments-webhook-signature for general payment webhook | General payment webhook has no signature header. Re-verify via Query API with paymentKey |
| Add Idempotency-Key to GET request | Idempotency-Key applies only to POST (ignored on GET) |
| Retry idempotent error by changing only the key | Inspect the error first before retry (risky) |
| Manage test/live keys in the same env variable | Separate by test_*/live_* prefix |
Pass amount field as a string | amount.value is integer, amount.currency is string ("KRW") |
Use requestPayment flow after billing key issuance | Billing key payment is server-only (/v1/billing/{billingKey}) |
| Treat confirm API failure as immediate payment failure | Check status with Query API before branching (may be already authenticated/approved) |
Call confirm API after failUrl redirect | failUrl is auth failure/cancellation. Do not call confirm API; show message by code |
Use email/phone/member ID directly as customerKey | Use unpredictable values (UUID; 2~300 chars; at least one special char). For non-members, ANONYMOUS |
Generate orderId in arbitrary format (whitespace/non-ASCII/etc.) | Letters/digits/-_, 6~64 chars |
Generate customerKey with 1 char (e.g., "a") | Min 2 chars, must include at least one special char (-_=.@) |
Scenario-specific full-code routing is in section 5 (entry points). This section is for meta/operations/reference information beyond scenarios.
| What you need | Page |
|---|---|
| Full API spec | API Reference |
| V2 JS SDK methods | SDK Reference |
| SDK error codes | Error Codes |
| Cancel/refund details | Cancel API |
| Payment query details | Query API |
| Webhook event spec | Webhook Events |
| Auth header details | Authorization Header |
| What you need | Page |
|---|---|
| Environment setup (test/live) | Environment Setup |
| API key issuance/management | API Keys Guide |
| Payment flow for humans | Payment Flow Guide |
| What you need | Page |
|---|---|
| Full docs index (LLM standard format) — primary routing resource | llms.txt |
| MCP server·LLM integration tools | LLMs Integration Guide |