Skip to main content
Every OTP you create in OTP.ID is a transaction with its own unique identifier, status, and expiry window. Understanding the lifecycle of that transaction — from the moment you call the API to the moment the OTP is verified or expires — helps you build reliable, user-friendly authentication flows. This page walks through each stage in order, explains the status values you will encounter, and documents the clamping rules that govern TTL and OTP length.

OTP Transaction ID Format

Every OTP transaction is assigned a unique ID at creation time. The format is:
Example: OTP20260807ABCD000001 Store the otp_id returned in the API response — you need it to poll the transaction status and to verify the OTP.

Transaction Statuses

The status field of a transaction takes one of the following values.
A failed delivery still creates a transaction record and still returns HTTP 200 OK. Check the status field in the response body to distinguish a failed delivery from a network or authentication error.
Expiry and attempt exhaustion are not status values. When the TTL elapses or the channel-specific attempt limit is reached, the stored status does not change (it stays sent/success) — instead, POST /v3/verify rejects the attempt with error code OTP_EXPIRED, TOO_MANY_ATTEMPTS, or ALREADY_USED (HTTP 422). The failed-attempt limit is 5 for standard channels and 2 for misscall. Use expires_at and attempts from GET /v3/otp/{otp_id} to detect these conditions when polling.

Lifecycle Flow

The diagram below describes the full journey of an OTP transaction in plain text.
1

Create the transaction

Call POST /v3/request (with automatic OTP generation or request-only flows) or POST /v3/send (supplying your own OTP value for whatsapp, sms, or email). Delivery happens synchronously — the API response already carries the delivery outcome (status: sent/success/failed) together with the otp_id. Only whatsapp_inbound starts at status: pending.For whatsapp_inbound, the response also includes a verification block containing wa_number, message, wa_link, and expires_at. Display this information to your user right away.
2

OTP.ID attempts delivery

OTP.ID contacts the channel vendor (WhatsApp provider, SMS gateway, email relay, or telephony provider). If the vendor accepts the message, the status advances to sent or success and credits are deducted. If the vendor rejects or cannot reach the destination, the status is set to failed and no charge is applied.
3

User receives the code

Your user reads the OTP from their WhatsApp message, SMS, email, missed-call log, or (for whatsapp_inbound) sends the pre-filled WhatsApp message. The OTP is valid until the TTL expires.
4

Your app submits the code for verification

When the user enters the code in your interface, call POST /v3/verify with the otp_id and the otp value the user provided.
5

OTP.ID returns the verification result

OTP.ID checks the submitted code and returns one of the following outcomes:
  • Correct code — HTTP 200, verified: true. Status advances to verified.
  • Wrong code (attempts remaining) — HTTP 200, verified: false, reason: "mismatch". The transaction remains active; the user can try again.
  • Attempt limit reached — the failed submission that reaches the limit returns HTTP 200 with verified: false; the transaction is then permanently locked. The next verification call returns HTTP 422 with TOO_MANY_ATTEMPTS. The limit is 5 failed attempts for standard channels and 2 for misscall.
  • Expired — HTTP 422, error code OTP_EXPIRED.
  • Already used — HTTP 422, error code ALREADY_USED.

Polling Transaction Status

If you need to check the state of a transaction outside of the verification flow (for example, to confirm delivery or to build a status dashboard), use the status endpoint:
Example response:
Poll this endpoint periodically for whatsapp_inbound transactions to detect when the user has sent their reply and the status advances to verified.

TTL Clamping

The ttl field controls how many seconds the OTP remains valid after creation. OTP.ID applies the following clamping rules silently — values outside the valid range are adjusted rather than rejected.
OTP.ID never returns a validation error for an out-of-range ttl. The value is silently clamped. Always read the expires_at field in the response to know the exact expiry time.

OTP Length Clamping

The otp_length field controls how many digits the generated OTP contains. The same silent clamping logic applies.

Verification Behaviour Reference

A wrong-code response is not a server error — it is a successful API call that returns verified: false. Do not treat HTTP 200 with verified: false as an exception or retry it automatically. Show the user a “wrong code” message and let them try again while attempts remain. The limit is 5 failed attempts for standard channels and 2 for misscall.
Build your verification UI to count remaining attempts on the client side. For standard channels, warn the user after 4 failed attempts; for Missed Call, warn after the first failed attempt because one attempt remains. You can also request a new OTP at any time if the user wants to start fresh, subject to the request rate limit for that channel.