Skip to main content

Verifying phone number - API only (v1)

Stable / GA

This is the current stable version of this step type.

Verifies that an end-user owns a given phone number by sending an SMS OTP​

Use when you need to confirm that a user owns a specific phone number as part of an onboarding or authentication flow. The Trust Platform sends a one-time code by SMS; your backend collects the code from the user and submits it via the sessions API. The step resolves once the code is validated, or when all retry attempts are exhausted.

This step has no Player UI. Your backend is responsible for presenting the OTP input screen to the end-user and submitting their response through the sessions API.

See Mid-workflow actions below.


Key features​

  • SMS OTP delivery: Sends a one-time code to the phone number provided in the UserContact input data block.
  • Configurable retries: Control how many times the user can attempt validation (maxValidationRetries) and how many new codes can be requested (maxGenerationRetries).
  • Resend support: The end-user can request a new code at any time while retries remain. Resending resets the validation retry counter.
  • Mid-workflow input: The step waits for your backend to submit an action via POST /action, keeping the flow paused until you respond.

Configuration​

OptionTypeRequiredDefaultDescription
senderstringNo—Sender ID shown on the SMS (max 14 characters). Optional — defaults to the provider default when omitted.
messageTemplatestringNo—Custom SMS message template. Must contain the {{otp}} placeholder exactly once. Maximum 160 characters. When omitted, the provider default template applies.
maxValidationRetriesliteralNo3Maximum number of OTP submission attempts per generated code. Accepted values: 3 or 5.
maxGenerationRetriesintegerNo5Maximum number of new OTP codes that can be generated per session. Once this limit is reached, further resendOtp requests are rejected.

Example configuration​

{
"maxValidationRetries": 3,
"maxGenerationRetries": 5
}

Input data blocks​

Data blockRequiredDescription
UserContactYesContains the phoneNumber field in E.164 format (e.g. +33612345678). Provided at session creation time.

Mid-workflow actions​

Session created
→ GET /step → START ← 400 if CREATED; { stepType: 'START' } if no active step yet
→ GET /step → PHONE_VERIFY:v1
→ POST /actions { validateOtp }
→ GET /step → PHONE_VERIFY:v1
or PHONE_VERIFY:v1 ← resendOtp if generationRetriesLeft > 0; else submit OTP to exit as not_verified
or PHONE_VERIFY:v1 ← no more codes; validateOtp accepted (non-expired code only)
or END (not_verified) ← if OTP expires while in generationLimitReached
or END (completed)
→ ...
→ GET /step → END (completed) → read flow execution result

GET /step​

While you have a session for a flow running that features this step, poll GET /step every 2-3 seconds to observe when the enduser reaches this step and discover which actions are available when they do.
Depending on the length & complexity of your flow, the enduser might be going through various steps, so keep polling this endpoint until stepType === PHONE_VERIFY:v1

stepType === PHONE_VERIFY:v1

The stepType field being PHONE_VERIFY:v1 is the authoritative signal for polling logic — branch on it.

Response shape
{
"stepType": "PHONE_VERIFY:v1",
"ticketId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"data": {
"validationRetriesLeft": 3,
"generationRetriesLeft": 4,
"otpExpiresAt": "2026-05-26T10:15:00Z",
"lastValidationResult": null
},
"actions": [
{
"type": "validateOtp",
"schema": {
"type": "object",
"properties": {
"type": { "const": "validateOtp" },
"otp": { "type": "string", "pattern": "^[0-9]{6}$" }
},
"required": ["type", "otp"]
}
},
{
"type": "resendOtp",
"schema": {
"type": "object",
"properties": {
"type": { "const": "resendOtp" }
},
"required": ["type"]
}
}
]
}

Response: data-object fields​

FieldTypeDescription
validationRetriesLeftintegerNumber of remaining OTP submission attempts for the current code. Present on all four states (otpSent, otpInvalid, otpExpired, generationLimitReached). In otpExpired it carries over the value from before expiry.
generationRetriesLeftintegerNumber of new codes that can still be generated. 0 in generationLimitReached.
otpExpiresAtstring (ISO 8601)Expiry timestamp of the current OTP. Present on all four states (otpSent, otpInvalid, otpExpired, generationLimitReached). In otpExpired it reflects the already-elapsed expiry time of the expired OTP.
lastValidationResultstring | nullOutcome of the last validation attempt.
Possible values of this field can be:
- null -> on first poll or after a successful resend.
- invalid -> OTP submitted but incorrect
- expired -> OTP was submitted after it expired
- generation_limit_exceeded -> All generation attempts exhausted
info

When all validation attempts for a given code are exhausted, the workflow immediately routes to not_verified and the session ends (stepType: 'END')

POST /action​

actions-array from GET /step

The actions array in the GET /step response carries a schema field — a JSON Schema object that describes the exact payload your backend must send to POST /actions. Use it to validate the request body before submitting.

Request headers
HeaderRequiredDescription
x-ticket-idYesValue of ticketId from the last GET /step response
OTP Expiration

The OTP expires after 5 minutes

Request for Action validateOtp
{ "type": "validateOtp", "otp": "123456" }
FieldTypeValidationDescription
otpstring6 digits ([0-9]{6})The code from the SMS
Request for Action resendOtp
{ "type": "resendOtp" }

Requests a new code. The validation retry counter resets to maxValidationRetries. Only available when generationRetriesLeft > 0.

Response: Success

202 Accepted — the action is queued. Poll GET /step to observe the updated state.

Response: Error
StatusCondition
400Request body failed schema validation (e.g. otp is not 6 digits)
401Missing or invalid Bearer token
403Token belongs to a different client than the session owner
404Session not found
409x-ticket-id mismatch (stale request), action not valid in current state, or generationRetriesLeft === 0 for resendOtp

Routes​

RouteDescription
verifiedPhone number successfully verified.
not_verifiedPhone number could not be verified (OTP limit exceeded).

Output data blocks​

RouteData blocks produced
verified—
not_verified—