Documentation
POST /verifications/exchange
Exchange the verification_token a mobile SDK produced for the recipient it proves. This is the only trustworthy answer to whether a user controls a number.
Exchange the verification_token a mobile SDK handed your app
for the recipient it proves. Call it from your own backend with a server key: a
publishable key is refused here, which is the whole point of the split.
This is the only trustworthy answer to “did this user prove they control this
number”. The matched field the device saw is a hint for its own UI, read off a
device you do not control, and an app can claim anything. Until you make this call,
your backend knows nothing.
curl -X POST https://api.otp.com/api/v1/verifications/exchange \
-H "Authorization: Bearer otp_live_•••" \
-H "Content-Type: application/json" \
-d '{"verification_token":"otp_vt_•••"}'
Request body
| Field | Type | Required | Description |
|---|---|---|---|
verification_token | string | yes | The token your app received when the verification succeeded on the device. Starts with otp_vt_, at most 128 characters. |
Tokens are short-lived and single-purpose, so exchange one as soon as your app hands it over rather than storing it.
Idempotency
Exchanging is idempotent for the same API key within the token lifetime: a retry
after a network failure returns the same result instead of losing the verification.
There is no idempotency-key header to send, because the token is already the key.
A different key of your own is not the same key, so route the retry through the same
one.
Response
{
"otp_id": "6f0d2c5e-…",
"recipient": "+14155552671",
"recipient_type": "phone",
"channel": "sms",
"verified_at": "2026-09-08T19:33:21Z"
}
| Field | Description |
|---|---|
otp_id | The verification this token belongs to. |
recipient | The recipient that was verified, in full. This is the answer the device could not be trusted to give you. |
recipient_type | phone or email. |
channel | The channel the verified code was delivered on: sms, whatsapp, telegram, or email. |
verified_at | When the end user entered the correct code, in UTC. |
Common errors
| Status | Type | When |
|---|---|---|
401 | HTTPException | Missing, invalid, or revoked key, or a publishable key used here. This endpoint takes a server key only. |
404 | HTTPException | The token is unknown, expired, already exchanged, or not yours. |
422 | ValidationError | Missing or malformed request body. |
Those four cases share one 404 on purpose: told apart, they would let anyone with
an API key probe which tokens exist. Treat a 404 as “there is no verification
here”, not as something to retry, and start a new verification instead.
See Error codes for the full list.