Issue lifecycle
Verification and delivery statuses, verify result codes, channels and templates
Each issue has a verification status, a delivery status, and a billing status. This page explains how they change and how to read them.
Issue options and defaults
| Field | Default | Range |
|---|---|---|
channel | sms | sms, alimtalk |
templateId | otp_default_kr | Templates |
expiresInSec | 180 (3 minutes) | 30–600 |
maxAttempts | 5 | 1–10 |
The code is a server-generated 6-digit number and is never returned by any API. Phone numbers are stored only as hashes and are never returned either.
Verification status
verificationStatus is one of:
| Value | Meaning |
|---|---|
issued | Issued, not yet verified, and not expired. |
verified | Verified. Cannot be verified again. |
expired | The expiry time has passed. |
max_attempts | Failed attempts reached the maximum. |
replaced | Invalidated by a newer issue for the same phone number and purpose. |
verify succeeds
issued ───────────────▶ verified
│ ├── expiry passed ──▶ expired
│ ├── failures reach maxAttempts ──▶ max_attempts
└──┴── re-issued for same phone + purpose ──▶ replacedVerification results
POST /v1/verify returns HTTP 200 even when verification fails. When verified is false, reasonCode tells you why.
reasonCode | Meaning | Suggested handling |
|---|---|---|
MISMATCH | Wrong code. Consumes one attempt. | Show attemptsRemaining and ask again. |
MAX_ATTEMPTS | No attempts left. | Ask the user to request a new code (re-issue with a new key). |
EXPIRED | The code expired. | Offer to re-issue. |
ALREADY_VERIFIED | The issue was already verified. | Treat as a duplicate submit and use the earlier result. |
REPLACED | A newer issue invalidated this one. | Verify with the latest issueId. |
NOT_FOUND | No issue with this issueId exists for your app. | Check how you store and pass issueId. |
A blank issueId, or a code that is not a 6-digit numeric string, returns 400 BAD_REQUEST.
Delivery status
deliveryStatus shows delivery progress and is updated asynchronously.
| Value | Meaning |
|---|---|
unknown | No tracking record yet, or it cannot be read right now. |
queued | Queued for sending. |
sent | Handed to the provider. |
delivered | Delivery confirmed. |
failed | Delivery failed. |
Checking status
Use GET /v1/status?issueId=... (sk_ key, otp:status) for a single issue.
curl "https://api.k-otp.dev/v1/status?issueId=0192f3c4-8b7a-7c3e-9a51-2f4d6e8b1a90" \
-H "Authorization: Bearer $KOTP_SECRET_KEY"{
"issueId": "0192f3c4-8b7a-7c3e-9a51-2f4d6e8b1a90",
"messageId": "0192f3c4-8b7b-7a01-8c2d-3e4f5a6b7c8d",
"purpose": "login",
"templateId": "otp_default_kr",
"verificationStatus": "issued",
"deliveryStatus": "delivered",
"overallStatus": "delivered",
"expiresAt": "2026-09-28T03:03:00.000Z",
"attemptsUsed": 0,
"maxAttempts": 5,
"attemptsRemaining": 5,
"createdAt": "2026-09-28T03:00:00.000Z",
"updatedAt": "2026-09-28T03:00:04.000Z"
}To show a single status in a UI, use overallStatus:
- If
verificationStatusis notissued, it is used as-is (verified,expired,max_attempts,replaced). - While
issued, it is derived fromdeliveryStatus:
deliveryStatus | overallStatus |
|---|---|
delivered | delivered |
failed | delivery_failed |
queued, sent | in_progress |
unknown | pending_lookup |
Good to know:
- Right after issuing, the record may still be persisting. For up to 60 seconds after issue you get
overallStatus: "pending_lookup"instead of404; after that a missing record returns404 NOT_FOUND. providerOutcomeAmbiguous: truemeans the provider outcome is unknown. Such sends are never retried automatically (Idempotency & retries).- An
issueIdbelonging to another app returns404, exactly like a missing one.
For many issues, use GET /v1/issues (otp:dashboard:read): newest-first cursor pagination (limit 1–100, default 50; pass the response's nextCursor as the next cursor) with verificationStatus and createdFrom/createdTo filters. See List OTP issues.
Channels and templates
channel selects SMS (default) or KakaoTalk AlimTalk. Message bodies come only from pre-registered templates, and every template contains the #{code} placeholder. AlimTalk is sent only with pre-registered provider template codes. If AlimTalk delivery fails, K-OTP automatically resends the same code by SMS at no extra credit; see Channels and failover.
templateId | Body |
|---|---|
otp_default_kr (default) | [K-OTP] 인증번호는 #{code}입니다. 3분 내에 입력해주세요. |
otp_login_kr | [K-OTP] 로그인 인증번호는 #{code}입니다. 3분 내에 입력해주세요. |
otp_signup_kr | [K-OTP] 회원가입 인증번호는 #{code}입니다. 3분 내에 입력해주세요. |
otp_payment_kr | [K-OTP] 결제 인증번호는 #{code}입니다. 3분 내에 입력해주세요. |
List templates and their variables with GET /v1/templates and GET /v1/templates/{templateId} (otp:templates). The server fills #{code}; never include code in templateVariables. New templates are registered on request, not through the API.
The "3분" (3 minutes) in the template bodies is fixed text. If you change expiresInSec, the
message and the real lifetime can differ.