POST /v3/request or POST /v3/send might time out before your code receives the response — but the server may have already processed it and charged your balance. If you retry blindly, you risk sending your user two OTP messages and paying twice. The external_id field solves this: by attaching your own unique key to a request, you make it safe to retry any number of times.
What external_id does
Pass a unique string in the external_id field of any POST /v3/request or POST /v3/send call. OTP.ID stores this key alongside the transaction for 24 hours. If you submit the same external_id again within that window with identical parameters (same channel, number, and — for /v3/send — the same otp), OTP.ID returns the original transaction record instead of creating a new one. No new message is sent to the user and no additional balance is deducted. This is called a replay.
Why it matters
Withoutexternal_id, a network timeout forces you to choose between two bad options: retry (risk a duplicate send) or give up (the user never gets verified). With external_id, retrying is always safe — you get back the original result.
Step-by-step example
1. First request
Your server sends the initial OTP request with a uniqueexternal_id tied to your own transaction or session.
2. Retry with the same parameters — replay
Your request timed out before you received the response above. You retry with the exact same body.otp_id, balance unchanged:
3. Same external_id, different parameters — 409 error
If you accidentally (or intentionally) use the same external_id with different parameters — a different channel, number, or otp value — OTP.ID rejects the request with HTTP 409 DUPLICATE_EXTERNAL_ID.
error.details.existing_otp_id field tells you the ID of the original transaction. Use GET /v3/otp/{otp_id} to retrieve its full details and determine the correct next step.
A
409 DUPLICATE_EXTERNAL_ID is not a send failure — it means an OTP
was already sent under this key. Inspect the original transaction before
deciding whether to issue a new one.Best practices
- Use a meaningful, unique key. Your order ID, session ID, checkout attempt UUID, or similar identifier works well. The key must be unique per OTP attempt — don’t reuse keys across different verification events.
- Always include
external_idin production. Transient network failures are common. Withoutexternal_id, any automated retry logic can send duplicate OTPs and charge your balance twice. - Treat
409as an inspection prompt, not an error. Fetch the original transaction withGET /v3/otp/{existing_otp_id}. If it’s still pending and not expired, you can direct the user to verify with that transaction. Only create a new OTP if the original has expired or already been used. - Keep keys unique per attempt. If a transaction expires and the user requests a new OTP, generate a fresh
external_id. Reusing the old one will cause a409(the 24-hour window has not yet elapsed).

