Skip to main content
Use POST /v3/request when you want OTP.ID to generate and deliver the one-time code on your behalf. You choose the channel, destination, and optional parameters — OTP.ID handles code generation and delivery. The code is never returned in the API response; it travels only to the end user via the chosen channel. On success you receive a transaction ID (otp_id) you’ll use later to verify what the user enters.

Endpoint

Request Headers

Request Body

string
required
The delivery channel for the OTP. Accepted values:
  • whatsapp — WhatsApp message
  • sms — SMS text message
  • email — Email message
  • misscall — Missed call (the OTP code is derived from the caller’s number)
  • whatsapp_inbound — User sends a WhatsApp message to OTP.ID to verify (see WhatsApp Inbound)
string
required
The destination phone number or email address. Required for all channels except whatsapp_inbound.
  • Phone numbers: digits only, minimum 10 characters (e.g. "6281234567890")
  • Email: a valid email address (e.g. "user@example.com")
string
Alternative field for the destination email address. Used as a fallback when number is empty and channel is email. If both number and email are provided, number takes precedence.
string
The brand name included in the OTP message (e.g. "MyApp"). Defaults to the brand_name configured on your merchant account.
integer
Number of digits in the generated OTP. Accepted range: 48. Defaults to 6. Values outside this range are clamped to the nearest bound — not rejected.
integer
Time-to-live in seconds before the OTP expires. Accepted range: 60900. Defaults to 300 (5 minutes). Values outside this range are clamped to the nearest bound — not rejected.
string
Your own idempotency key for this transaction. If you submit a request with an external_id that already exists, OTP.ID returns the original otp_id without sending a new message and without changing your balance. See Idempotency below.

Response Fields

string
The unique transaction ID assigned by OTP.ID. Use this value in subsequent calls to POST /v3/verify and GET /v3/otp/{otp_id}. Format: OTP + YYYYMMDD + 4 letters + 6 digits (e.g. OTP20260807ABCD000001).
string
Current delivery status of the transaction:
  • sent / success — code was delivered to the destination
  • pending — used for whatsapp_inbound while awaiting the user’s reply
  • failed — vendor delivery failed (see Delivery Failures)
string
The channel used to deliver the OTP, echoed from your request.
string
The destination address used, echoed from your request.
integer
Transaction cost in IDR. This field is present even when delivery fails. No charge is applied when delivery fails — see Delivery Failures.
integer
Your account balance in IDR after this transaction. Unchanged if the delivery failed or if this was an idempotency replay.
string
The datetime at which this OTP expires, in YYYY-MM-DD HH:MM:SS format (WIB, UTC+7).
object
Present only for whatsapp_inbound transactions. Contains the instructions your user needs to verify themselves.

Examples

WhatsApp

Success response:

Email

WhatsApp Inbound

Success response (whatsapp_inbound):
For whatsapp_inbound, do not pass a number. Show the user the verification.wa_link (or the wa_number + message pair) and have them tap/send it from their own WhatsApp. OTP.ID matches the inbound message to this transaction automatically — no call to POST /v3/verify is needed.

Special Behaviors

Delivery Failures

When the OTP.ID vendor accepts your request but ultimately fails to deliver the message (e.g. the number is unreachable), the API still returns HTTP 200 with "status": "failed". Your balance is not deducted. Check data.status in your application logic — do not rely solely on the HTTP status code. Example — delivery failure (HTTP 200):

Idempotency

If you provide an external_id and a request with that ID was already processed, OTP.ID returns the original otp_id in the response without sending a new message and without changing last_balance. This lets you safely retry requests after network timeouts. The response body is otherwise identical to the original.

WhatsApp Inbound

When channel is whatsapp_inbound, the number field is optional. Omitting it means OTP.ID will accept the verification reply from any WhatsApp number that sends the matching token — useful when you do not yet know the user’s number. The transaction transitions to verified automatically when the matching message arrives.

Error Responses

All errors follow the shape {"success": false, "data": null, "error": {"code": "...", "message": "..."}}. Example — insufficient balance:
Example — duplicate external_id collision: