/The verification object

API docs

The verification object

A single verification represents one OTP session - its destination, delivery attempts and lifecycle status - and is the object OTPRelay returns from every verification endpoint.

The same verification object is returned when you send a verification (POST /v1/verifications), check one (POST /v1/verifications/check), and when you retrieve it (GET /v1/verifications/{id}). Check responses are a compact view of the same resource - they always include status and the relevant convenience boolean, and a full retrieve returns every attribute below.

verification
{  "id": "ver_01HZX9Q2K3M7B8N4P5R6S7T8U9",  "object": "verification",  "status": "pending",  "to": "+966512345678",  "country": "SA",  "sender": "OTPRelay",  "code_length": 6,  "verified": false,  "attempts": [    {      "id": "att_01HZX9Q2K3M7B8N4P5R6S7T8UA",      "status": "undelivered",      "carrier": "stc",      "at": "2026-06-16T10:00:00Z"    },    {      "id": "att_01HZX9Q2K3M7B8N4P5R6S7T8UB",      "status": "delivered",      "carrier": "mobily",      "at": "2026-06-16T10:00:11Z"    }  ],  "webhook_url": "https://example.com/otprelay/webhook",  "metadata": { "user_id": "u_8842" },  "created_at": "2026-06-16T10:00:00Z",  "expires_at": "2026-06-16T10:10:00Z"}

The attempts[] array records every SMS send OTPRelay made for this verification, in order. In the example above the first attempt over stc went undelivered, so SMS failover automatically retried the same code over an alternate route - handled here by mobily - which was delivered. Every attempt is SMS; they differ only by carrier.

Attributes

idstringoptional
Unique identifier for the verification, prefixed ver_.
objectstringoptional
Always "verification".
statusstringoptional
The lifecycle state - one of pending, approved, expired, canceled, max_attempts_reached or failed. The source of truth for whether the user is verified. See Statuses.
tostringoptional
The destination phone number in E.164 format, e.g. +966512345678.
countrystringoptional
ISO 3166-1 alpha-2 country code inferred from to, e.g. SA.
senderstringoptional
The alphanumeric sender every code is delivered from. Always "OTPRelay" - a single pre-cleared shared sender, with nothing for you to register. See Sender & message.
code_lengthintegeroptional
Number of digits in the generated code, 4-8.
verifiedbooleanoptional
Convenience flag returned by check: true when the submitted code was correct. Mirrors status: "approved".
attemptsarrayoptional
Each send OTPRelay made for this verification, in order. See The attempt object.
webhook_urlstring | nulloptional
The HTTPS URL this verification's events are POSTed to, if one was set on the request. See Webhooks.
metadataobjectoptional
The key/value pairs you attached on send, echoed back here and on webhook events.
created_atstringoptional
ISO 8601 timestamp when the verification was created.
expires_atstringoptional
ISO 8601 timestamp when the code expires. After this, status becomes expired.
checked_atstringoptional
ISO 8601 timestamp of the most recent check. Present once the verification has been checked at least once.

The attempt object

Each entry in attempts[] describes one SMS delivery attempt. Route and operator retries each add an entry, so the array is your audit trail of exactly what OTPRelay did to deliver the code.

idstringoptional
Unique identifier for the attempt, prefixed att_.
statusstringoptional
Delivery state of the attempt - sent, delivered, undelivered or failed. A status of undelivered or failed is what triggers an automatic retry over an alternate route/operator.
carrierstringoptional
The mobile operator that handled the attempt, e.g. stc, mobily, du or ooredoo.
atstringoptional
ISO 8601 timestamp when the attempt was made.

Statuses

A verification starts as pending and moves to exactly one terminal state. Once it leaves pending it is closed: further checks return 404 and you must start a new verification to retry.

statusmeaning
pendingThe code has been sent and is awaiting a correct check. The only non-terminal state.
approvedThe user submitted the correct code. verified is true. Terminal.
expiredThe code reached expires_at before a correct check. Terminal.
canceledYou canceled the verification before it was approved. Terminal.
max_attempts_reachedFive incorrect checks were made. The verification is closed. Terminal.
failedSMS delivery failed across every route and operator OTPRelay tried. Terminal.

status is the source of truth

Always branch on status. The verified boolean is a convenience flag that mirrors status: "approved" - it exists so a simple if (check.verified) reads cleanly, but it never carries information statusdoesn't.

Retrieve, cancel and list

Retrieve a verification at any time to read its current status and the full attempts[] history - useful for reconciling state outside the send/check flow or alongside webhooks.

GET/v1/verifications/{id}
200 OK
{  "id": "ver_01HZX9Q2K3M7B8N4P5R6S7T8U9",  "object": "verification",  "status": "approved",  "verified": true,  "to": "+966512345678",  "checked_at": "2026-06-16T10:01:12Z",  "created_at": "2026-06-16T10:00:00Z",  "expires_at": "2026-06-16T10:10:00Z"}

Cancel an in-flight verification to close it early - for example when the user requests a fresh code. This sets status to canceled; the old code stops being accepted immediately.

POST/v1/verifications/{id}/cancel
200 OK - canceled
{  "id": "ver_01HZX9Q2K3M7B8N4P5R6S7T8U9",  "object": "verification",  "status": "canceled",  "to": "+966512345678",  "sender": "OTPRelay",  "created_at": "2026-06-16T10:00:00Z"}

To page through past verifications, list them and filter by status, to or created_at.

GET/v1/verifications

To create one, see Send a verification. To understand how the attempts[] array gets populated by SMS route and operator failover, see Delivery & failover.