Integrate One-Time Passcode (OTP)
Integrate Socure's OTP service with steps for sending, delivery, verification, and decision handling in authentication and fraud prevention workflows.
Before you start
Make sure your RiskOS™ environment is provisioned with:
Postman Collection
Use the following Postman collection to test the OTP enrichment with the Evaluation endpoint.
Testing Failure Paths in Sandbox:
Use these "Magic Values" to force specific error responses:
- Force Delivery Failure: Use phone number
+11234567890.- Force Invalid Code: Enter
000000during the verification step. (Any other 6 digits will succeed).
Choose your environment
Start with Sandbox for development and testing, then move to Production for live applications.
https://riskos.sandbox.socure.com/api/evaluation- No real customer data
- Free testing environment
- Unlimited API calls
https://riskos.socure.com/api/evaluation- Real customer data
- API usage charges apply
- Connects to live data services
Get an API key
- In the Sandbox RiskOS™ Dashboard, go to Developer Workbench > API Keys.
- Copy your API key securely.
- In the Production RiskOS™ Dashboard, go to Developer Workbench > API Keys.
- Copy your API key securely.
How it works
The OTP integration follows an asynchronous pattern. Unlike a standard identity check, the API pauses and waits for the user to enter their code.
- Initiate delivery: Send a
POST /api/evaluationrequest with the user’s contact information (phone number and/or email). - Wait State: RiskOS™ creates a verification session and delivers the 6-digit code via SMS, Voice, or Email. The API returns an
ON_HOLDstatus and a uniqueeval_id. - Verify and Resume: Once the user enters the code into your UI, submit a
PATCH /api/evaluation/{eval_id}request to validate the code. - Final Decision: RiskOS™ resumes the workflow, applies final scoring logic, and returns a terminal decision (
ACCEPT,REJECT, orREVIEW).
Integration flow
Your integration logic must handle the "In-Between" states, such as a user requesting a code resend or deciding to switch from an SMS delivery to an email delivery if they have poor cell reception.
sequenceDiagram
participant User
participant ClientApp
participant RiskOS™
User->>ClientApp: Enter phone/email
ClientApp->>RiskOS™: POST /evaluation
Note right of RiskOS™: Pauses evaluation
RiskOS™-->>ClientApp: 200 OK (status: ON_HOLD, sub_status: Pending OTP Code)
RiskOS™->>User: Deliver OTP (SMS/Email/Voice)
User->>ClientApp: Input 6-digit code
ClientApp->>RiskOS™: PATCH /evaluation/{eval_id} (otp.code: "123456")
alt OTP Valid
RiskOS™-->>ClientApp: 200 OK (status: CLOSED, decision: ACCEPT)
else OTP Invalid/Expired
RiskOS™-->>ClientApp: 400 Bad Request (msg: "Invalid code")
opt User requests Resend
ClientApp->>RiskOS™: PATCH /evaluation/{eval_id} (otp.resend: true)
RiskOS™->>User: Re-deliver same code
end
opt User switches Channel (e.g., SMS to Email)
ClientApp->>RiskOS™: PATCH /evaluation/{eval_id} (actions.resume: true)
ClientApp->>RiskOS™: POST /evaluation (new email payload)
RiskOS™->>User: Deliver new code to Email
end
end
Quick response reference
Use this table to map RiskOS™ API responses to your application's routing and logic.
| User Action | status | sub_status | decision | Your Application Action |
|---|---|---|---|---|
| Initial POST (Success) | ON_HOLD | Pending OTP Code | REVIEW | Transition UI to the 6-digit code entry screen. |
| Enters correct OTP (KYC Complete) | CLOSED | Approve | ACCEPT | Redirect to Success/Onboarding Complete page. |
| Enters correct OTP (KYC Incomplete) | ON_HOLD | More information needed | REVIEW | Prompt user for manual PII (Name, Address, SSN) and PATCH evaluation using eval_id. |
| Enters incorrect OTP | ON_HOLD | Pending OTP Code | REVIEW | Show "Incorrect code" error; display remaining attempts. |
| 5 failed attempts | CLOSED | Max attempts reached | REJECT | Disable inputs; start 10-minute cooling period timer. |
| Requests OTP resend | ON_HOLD | Pending OTP Code | REVIEW | Notify user that a new message was sent; keep UI on entry screen. |
| Switches channel | ON_HOLD | Pending OTP Code | REVIEW | Call PATCH (resume: true) to clear the hold, then a new POST for the new channel. |
| Session expires | CLOSED | Decline | REJECT | The 10-minute OTP window has elapsed. The enrichment-level response.status returns "reject" because the verification session was destroyed on expiration. Show a "Session Expired" screen and prompt the user to restart with a new evaluation. |
One-Time Passcode implementation timeline
Step 1: Initiate delivery (POST)
POST)Submit the applicant's contact details to start the session. This triggers the initial OTP delivery via SMS, Voice, or Email depending on your workflow configuration.
- The "Wait" State: Because OTP is an asynchronous process, RiskOS™ pauses the evaluation. You receive a
REVIEWdecision and anON_HOLDstatus.
Key requirements
- SMS/Voice OTP: Include
phone_numberin E.164 format (for example, +14155550100). - Email OTP: Include
emailaddress in valid RFC-5322 format (for example, [email protected]).
Example request
curl -X POST https://riskos.sandbox.socure.com/api/evaluation \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "otp_session_123",
"workflow": "your_otp_workflow",
"data": {
"individual": {
"phone_number": "+14155550100",
"email": "[email protected]"
}
}
}'Step 2: Handle the "On-Hold" response
Your application must capture the eval_id from the response. This ID is the unique "key" for the user's current session. By default, the OTP remains active for 10 minutes. If the user doesn't authenticate with that OTP within that time frame, you must execute a new OTP request in the workflow and get a new eval_id.
- UI Action: When your frontend sees the
"sub_status": "Pending OTP Code", transition to the 6-digit code entry screen.
Example response
{
"decision": "REVIEW",
"eval_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "ON_HOLD",
"sub_status": "Pending OTP Code"
}Step 3: Manage "In-Between" UX logic
While the evaluation is ON_HOLD, use the eval_id to handle non-linear user actions without breaking the session.
resendvsresume— these are different actions:
- Use
otp.resendto re-deliver the same code on the same channel. It doesn't skip the OTP step.- Use
actions.resumeto skip the paused OTP step (for example, to switch delivery channels). It doesn't send a code.
Scenario: User didn't receive the code (Resend)
If the user requests a resend, do not initiate a new evaluation. Use a PATCH request with the resend flag to deliver the same 6-digit code.
- Same-Code Guarantee: Re-sends the existing code to ensure both the original and new messages are valid if they arrive out of order.
- Persistence: Re-sending the code does not reset the 5-attempt verification limit.
- TTL Constraint: This does not extend the 10-minute session expiration. If the original session has 2 minutes remaining, the resent code will also expire in 2 minutes.
curl -X PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "resend_request_001",
"timestamp": "2026-02-13T16:38:37Z",
"workflow": "your_otp_workflow",
"data": {
"individual": {
"otp": { "resend": true }
}
}
}'Scenario: User made a typo (change number)
If a user needs to change their destination address (phone or email), follow this two-step sequence to ensure the session updates correctly.
1. Clear the session. Send a PATCH request with "resume": true to tell RiskOS™ to stop waiting for the old code:
curl -X PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "clear_session_typo",
"workflow": "your_otp_workflow",
"actions": { "resume": true }
}'2. Initiate new evaluation. Submit a fresh POST with the updated phone_number or email:
curl -X POST https://riskos.sandbox.socure.com/api/evaluation \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "new_otp_attempt_002",
"workflow": "your_otp_workflow",
"data": {
"individual": {
"phone_number": "+14155559999"
}
}
}'Scenario: Switching channels (SMS to Email)
If a user has poor cell reception and wants an email instead, you must pivot the delivery method mid-stream.
Note: Users can typically only change channels once per session.
1. Resume. Call PATCH with "resume": true to bypass the active SMS step.
2. Initiate Email. Call POST /api/evaluation with the user's email address.
curl -X PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "clear_session_switch",
"workflow": "your_otp_workflow",
"actions": { "resume": true }
}'Scenario: OTP session expires (10-minute window elapses)
Each OTP session has a 10-minute time-to-live (TTL) that starts when the code is first delivered. If the user does not submit a valid code via PATCH within this window, the OTP provider destroys the verification session and the evaluation closes.
What happens when the session expires
- The OTP provider destroys the verification session resource after 10 minutes.
- If a
PATCHwithotp.codeis submitted after expiration, the enrichment-levelresponse.statusreturns"reject". - The evaluation closes with
status: "CLOSED",sub_status: "Decline", anddecision: "REJECT".
You cannot generate a new OTP session within the same
eval_idafter expiration.Once the OTP step reaches a terminal status (
"reject"due to expiration or max attempts), the workflow step completes and the evaluation is finalized. To retry OTP verification, you must start a new evaluation with a freshPOST /api/evaluationrequest, which generates a neweval_idand a new OTP session.
How to distinguish expiration from max attempts
The API does not return a separate "timeout" or "expired" status flag. Both expiration and max-attempt failures result in a "reject" at the enrichment level. Use these fields to distinguish between the two:
| Cause | sub_status | response.attemptCount | Indicator |
|---|---|---|---|
| Session expired | Decline | Typically low (1–2) | Low attempt count with "reject" status indicates the session window elapsed before the user completed verification. |
| Max attempts exceeded | Max attempts reached | 5 | The user exhausted all 5 verification attempts. The API also returns a 400 status code with the message "Max verify attempts reached". |
Recovery steps
- Detect the expired session by checking for
status: "CLOSED"andsub_status: "Decline". - Display a "Session expired" message in your UI and prompt the user to try again.
- Submit a new
POST /api/evaluationrequest with the user's contact details to start a fresh OTP session. - Use the new
eval_idfrom the response for all subsequentPATCHrequests.
Example expired-session response
{
"decision": "REJECT",
"eval_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "CLOSED",
"sub_status": "Decline",
"data_enrichments": [
{
"enrichment_name": "Verify OTP SMS",
"enrichment_provider": "SocureOTP",
"status_code": 200,
"request": {
"otp": "691573",
"verificationId": "a8ae056a-497d-41b3-ac43-66613506c9ba"
},
"response": {
"attemptCount": 1,
"status": "reject",
"verificationId": "a8ae056a-497d-41b3-ac43-66613506c9ba"
}
}
]
}Proactive UX tip: Display a countdown timer in your UI showing the remaining time in the 10-minute window. If the timer is about to expire, prompt the user to request a resend (which re-delivers the same code but does not extend the TTL). This gives the user a fresh notification and reduces the chance of a timeout.
Scenario: Max attempts reached (Lockout)
Users have 5 attempts maximum to enter the correct code. This limit is persistent across the entire session; requesting a code resend does not reset the attempt counter. After the 5th failure, the API returns a 400 Bad Request and terminates the session.
- API Message:
"Max verify attempts reached" - Cooling Period: A 10-minute lockout is enforced per destination (phone or email).
- UI Action: Disable input fields/buttons, display a 10-minute countdown timer, and prevent new
POSTrequests for that specific destination until the timer expires.
Example error response
{
"status": 400,
"error": "Bad Request",
"message": "Max verify attempts reached"
}Step 4: Verify the code (PATCH)
PATCH)Once the user enters the code into your interface, submit it back to RiskOS™ to resume the evaluation. You must use the eval_id captured in Step 2: Handle the "On-Hold" response within the URL path.
This request validates the code and automatically triggers the next steps in your workflow (for example, final risk scoring or fraud checks).
curl -X PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "verify_step_001",
"timestamp": "2026-02-13T16:38:37Z",
"workflow": "your_otp_workflow",
"data": {
"individual": {
"otp": { "code": "123456" }
}
}
}'Step 5: Receive final decision
If the code is correct, RiskOS™ automatically resumes the workflow to perform final scoring. The session typically moves from ON_HOLD to a terminal CLOSED status.
A successful OTP verification typically results in an ACCEPT decision, though this depends on your specific workflow rules and other risk signals.
- Success is not always the end: If the response returns
status: "ON_HOLD"withsub_status: "More information needed", device possession is verified, but you must nowPATCHthe evaluation with the user's manual PII (Name, Address, SSN, etc.) to complete the identity profile. - Implementation note: If the evaluation results in a
REJECTorREVIEWdecision even after successful OTP verification, check your workflow's auxiliary risk modules (like Phone Risk or Digital Intelligence) for high-risk signals.
Example response
Note: The following examples show a representative subset of the response. The actual response includes additional fields such as
workflow_id,workflow_version,eval_source,eval_start_time,eval_end_time,score, andreview_queues. To include the original input data (including address details), pass theinclude_input=truequery parameter when retrieving the evaluation withGET /api/evaluation/{eval_id}.
{
"id": "verify_attempt_001",
"eval_id": "123e4567-e89b-12d3-a456-426614174000",
"workflow": "account_takeover",
"eval_status": "evaluation_completed",
"status": "CLOSED",
"sub_status": "Approve",
"decision": "ACCEPT",
"tags": [
"OTP_VERIFIED",
"Low Risk"
],
"data_enrichments": [
{
"enrichment_name": "Socure OTP",
"enrichment_provider": "Socure",
"status_code": 200,
"response": {
"verificationId": "b8fbeb31-a2fc-4f1e-a5d1-392c4ed06538",
"status": "approved",
"attemptCount": 1
},
"is_source_cache": false,
"total_attempts": 1
}
]
}{
"id": "verify_attempt_001",
"eval_id": "123e4567-e89b-12d3-a456-426614174000",
"workflow": "account_takeover",
"eval_status": "evaluation_paused",
"status": "ON_HOLD",
"sub_status": "More information needed",
"decision": "REVIEW",
"tags": [
"OTP Approved",
"Prefill Unsuccessful"
]
}API response vs. Dashboard view:
The
PATCHresponse returns evaluation metadata and enrichment results — it doesn't echo back the submitteddataobject (for example, address or other PII fields). If the RiskOS™ Dashboard shows adatafield in its "raw response" view, that reflects the Dashboard's combined view of input and output, not the literal API response payload.To retrieve the original input data programmatically, call
GET /api/evaluation/{eval_id}?include_input=true. Theinputfield in the response containsdata(as submitted) andupdated_data(after workflow transformations). See API Responses for details.
State and routing reference
Use the following table to map RiskOS™ response fields to your frontend routing logic. This ensures your UI always reflects the current state of the user's verification session.
sub_status | status | UX Meaning | UI Action |
|---|---|---|---|
| Pending OTP Code | ON_HOLD | The code has been delivered successfully. | Transition from "Processing" to the 6-digit code entry screen. |
| Max attempts reached | CLOSED | Security lockout due to 5 failed attempts. | Disable inputs and show a 10-minute countdown timer. |
| Approve | CLOSED | The code was correct and the user is low risk. | Redirect to your Success/Onboarding Complete page. |
| Decline | CLOSED | The session expired or the user is high risk. | Show a "Verification Failed" screen with an option to restart. |
Handling the eval_id
eval_idYour frontend state management (Redux, Context API, etc.) must persist the eval_id as long as the session is ON_HOLD.
Persistence note:
If the user refreshes their browser, you must retrieve the
eval_idfrom your local storage or backend to ensure they can resume the same session without triggering a new (rate-limited) OTP request.
Technical appendix: API reference
The following schema definitions cover the /api/evaluation endpoint when used with the OTP enrichment.
1. Initiation delivery schema (POST)
POST)Use these fields to start a new verification session.
Initiation delivery
Endpoint
POST https://riskos.sandbox.socure.com/api/evaluationPOST https://riskos.socure.com/api/evaluationAuthentication and headers
Include your API key in the Authorization header as a Bearer token, along with standard JSON headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Accept: application/json
X-API-Version: 2025-01-01.orion # optional – pins a specific API versionExample request
{
"id": "otp-001",
"timestamp": "2025-09-30T10:00:00Z",
"workflow": "individual_onboarding",
"data": {
"individual": {
"phone_number": "+14155550100",
"email": "[email protected]"
}
}
}curl -X POST https://riskos.sandbox.socure.com/api/evaluation \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "otp-001",
"timestamp": "2025-09-30T10:00:00Z",
"workflow": "individual_onboarding",
"data": {
"individual": {
"phone_number": "+14155550100",
"email": "[email protected]"
}
}
}'Request schema
Top-level fields
<th>
Type
</th>
<th>
Required
</th>
<th>
Description
</th>
<th>
Example
</th>
</tr> <td>
String
</td>
<td>
Required
</td>
<td>
Required, customer-defined unique identifier for the request.
This value must be unique for each evaluation. Reusing an ID causes RiskOS™ to treat the request as a re-run and can impact processing behavior, results, and downstream workflows.
</td>
<td>
`"a86580cc-1733-4188-86b5-717166e1db8c"`
</td>
</tr>
<tr>
<td>
`timestamp`
</td>
<td>
String `<Date-Time>`
</td>
<td>
Required
</td>
<td>
RFC 3339 timestamp indicating when the evaluation request was initiated by your system.
</td>
<td>
`"2025-05-02T12:44:22.059Z"`
</td>
</tr>
<tr>
<td>
`workflow`
</td>
<td>
String
</td>
<td>
Required
</td>
<td>
Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > **Developer Workbench** > **Integration Checklist**. <br /><br /> **Note:** The workflow name must be unique within your RiskOS™ environment. It is not scoped by use case and must identify a single active workflow for each Evaluation API request.
</td>
<td>
`"consumer_onboarding"`
</td>
</tr>
<tr>
<td>
`data`
</td>
<td>
Object
</td>
<td>
Required
</td>
<td>
Main payload containing consumer information, device data, and event details for evaluation.
</td>
<td></td>
</tr>
<tr>
<td>
→`individual`
</td>
<td>
Object
</td>
<td>
Required
</td>
<td>
Primary identity object containing individual's information.
</td>
<td>
See `individual` schema below.
</td>
</tr>
individual fields
individual fields| Field | Type | Required | Description | Example |
|---|---|---|---|---|
email | String | Required | Consumer's email address (must be a valid email format) | "[email protected]" |
phone_number | String | Required | Consumer's phone number in E.164 format. The API expects the standard E.164 format but tolerates hyphens and spaces for user convenience. | "+1-667-368-1976" |
Initiation delivery response
Example response
{
"decision": "REVIEW",
"eval_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "ON_HOLD",
"sub_status": "Pending OTP Code",
"data_enrichments": [
{
"enrichment_name": "Deliver OTP SMS",
"enrichment_provider": "SocureOTP",
"status_code": 200,
"response": {
"verificationId": "aaa4703c-16b6-4a25-9c70-58722a26c7ed"
}
}
]
}Response schema
data_enrichments fields - OTP Delivery (Initial POST)
data_enrichments fields - OTP Delivery (Initial POST)This object appears in the response of the initial POST /api/evaluation call. Use these fields to confirm the code was successfully handed off to the delivery provider.
| Field | Type | Description | Example |
|---|---|---|---|
enrichment_name | String | Name of the delivery enrichment. | "Deliver OTP SMS" |
enrichment_endpoint | String | API endpoint used to trigger the delivery. | "https://service.socure.com/api/3.0/otp/send" |
enrichment_provider | String | Provider handling the delivery. | "SocureOTP" |
status_code | Number | HTTP status from the provider (200 = Success). | 200 |
request.destination | String | The phone number or email where the code was sent. | "+14155550100" |
response.verificationId | String | The unique ID for the delivery attempt (Required for Support). | "aaa4703c-16b6-4a25-9c70-58722a26c7ed" |
is_source_cache | Boolean | Whether data was cached (Always false for OTP). | false |
2. Management and verification schema (PATCH)
PATCH)Use these fields to handle user actions while the evaluation is ON_HOLD. All management actions require the eval_id captured from the initial delivery response.
Handling channel switches: To switch from SMS to Email delivery, you must first "clear" the paused state.
- Send a
PATCHwith "actions": { "resume": true }.- Once the session is resumed, send a new
POST /api/evaluationwith the new contact method.
Management & verification
Endpoint
PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id}PATCH https://riskos.socure.com/api/evaluation/{eval_id}Authentication and headers
Include your API key in the Authorization header as a Bearer token, along with standard JSON headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Accept: application/json
X-API-Version: 2025-01-01.orion # optional – pins a specific API versionExample request
{
"id": "otp-resend-001",
"timestamp": "2025-09-30T10:07:00Z",
"workflow": "individual_onboarding",
"data": {
"individual": {
"otp": {
"resend": true
}
}
}
}curl -X PATCH https://riskos.sandbox.socure.com/api/evaluation/{eval_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"id": "otp-resend-001",
"timestamp": "2025-09-30T10:07:00Z",
"workflow": "individual_onboarding",
"data": {
"individual": {
"otp": {
"resend": true
}
}
}
}'Request schema
Top-level fields
<th>
Type
</th>
<th>
Required
</th>
<th>
Description
</th>
<th>
Example
</th>
</tr> <td>
String
</td>
<td>
Required
</td>
<td>
Required, customer-defined unique identifier for the request.
This value must be unique for each evaluation. Reusing an ID causes RiskOS™ to treat the request as a re-run and can impact processing behavior, results, and downstream workflows.
</td>
<td>
`"a86580cc-1733-4188-86b5-717166e1db8c"`
</td>
</tr>
<tr>
<td>
`timestamp`
</td>
<td>
String `<Date-Time>`
</td>
<td>
Required
</td>
<td>
RFC 3339 timestamp indicating when the evaluation request was initiated by your system.
</td>
<td>
`"2025-05-02T12:44:22.059Z"`
</td>
</tr>
<tr>
<td>
`workflow`
</td>
<td>
String
</td>
<td>
Required
</td>
<td>
Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > **Developer Workbench** > **Integration Checklist**. <br /><br /> **Note:** The workflow name must be unique within your RiskOS™ environment. It is not scoped by use case and must identify a single active workflow for each Evaluation API request.
</td>
<td>
`"consumer_onboarding"`
</td>
</tr>
<tr>
<td>
`actions`
</td>
<td>
Object
</td>
<td>
Optional
</td>
<td>
A top-level container for instructions that modify the lifecycle of the current evaluation session.
</td>
<td>
`{"actions": {...}}`
</td>
</tr>
<tr>
<td>
→ `resume`
</td>
<td>
Boolean
</td>
<td>
Required
</td>
<td>
Set to `true` to force the workflow out of an `ON_HOLD` state. This bypasses the current pending step (e.g., OTP entry) to allow the user to fix a typo or switch delivery channels.
</td>
<td>
`true`
</td>
</tr>
<tr>
<td>
`data`
</td>
<td>
Object
</td>
<td>
Required
</td>
<td>
Main payload containing consumer information, device data, and event details for evaluation.
</td>
<td></td>
</tr>
<tr>
<td>
→`individual`
</td>
<td>
Object
</td>
<td>
Required
</td>
<td>
Primary identity object containing individual's information.
</td>
<td>
See `individual` schema below.
</td>
</tr>
individual.otp fields
individual.otp fields| Field | Type | Action | Description |
|---|---|---|---|
code | String | Verify | The 6-digit code entered by the user. |
resend | Boolean | Resend | Set to true to re-deliver the same code to the same destination. |
3. Response and status reference
Use the following fields to manage frontend routing and determine the session state.
Response & status
Example response
{
"decision": "ACCEPT",
"status": "CLOSED",
"sub_status": "Approve",
"tags": ["OTP_VERIFIED"],
"data_enrichments": [
{
"enrichment_name": "Verify OTP SMS",
"enrichment_provider": "SocureOTP",
"status_code": 200,
"response": {
"attemptCount": 1,
"status": "approved",
"verificationId": "aaa4703c-16b6-4a25-9c70-58722a26c7ed"
}
}
]
}Note: The API response does not include the
dataobject you submitted in the request (for example,data.individual.address). The response contains only evaluation metadata, decision fields, anddata_enrichments. The RiskOS™ Dashboard may display a combined view that includes input data alongside the response.To retrieve the original input data, use
GET /api/evaluation/{eval_id}?include_input=true.
Response schema
Top-level Fields
| Field | Type | Description | Example |
|---|---|---|---|
id | String | Unique evaluation identifier provided in your request. | "OTP-Test" |
workflow | String | RiskOS™ workflow name used for the evaluation. | "individual_onboarding" |
workflow_id | String | The unique UUID of the specific workflow definition used. | "2049b724-0461-4a4b-8526-6c26a0c1e88a" |
workflow_version | String | The specific version of the workflow logic applied to this session. | "8.13.0" |
eval_source | String | Origin of the evaluation invocation (e.g., "API"). | "API" |
eval_start_time | String | RFC 3339 timestamp indicating when the evaluation session began. | "2025-09-30T10:00:00Z" |
eval_end_time | String | RFC 3339 timestamp indicating when the evaluation ended or paused. | "2025-09-30T10:00:03Z" |
decision | String (enum) | Final evaluation result. Possible values: • ACCEPT• REVIEW• REJECTNote: The fields returned can be customized to fit your integration or business needs. | "REJECT" |
decision_at | String (RFC 3339) | Timestamp of the final decision. | "2025-09-26T21:31:27.662218159Z" |
status | String (enum) | Case-level status of the evaluation. Possible values: • OPEN• CLOSED | "CLOSED" |
sub_status | String | Provides additional detail about the evaluation status. Example values: • Under Review• Pending Verification• Accept• Reject | "Accept" |
tags | Array of Strings | Descriptive labels summarizing the decision context. | ["OTP Unsuccessful"] |
review_queues | Array of Strings | Queues used for routing manual reviews. | ["Default Queue"] |
data_enrichments | Array of Objects | List of enrichment calls (for example, OTP send/verify attempts), including request/response details and provider metadata. | — |
eval_status | String (enum) | Internal RiskOS™ evaluation lifecycle state. • evaluation_paused: The API is waiting for user input (the "Wait State").• evaluation_in_progress: The engine is currently processing a code verification.• evaluation_completed: The workflow has finished and a terminal decision is reached. | "evaluation_completed" |
environment_name | String | Indicates which environment the evaluation ran in. Typically Sandbox for testing or Production for live traffic. | "Sandbox" |
data_enrichments fields - OTP Verification
data_enrichments fields - OTP Verification| Field | Type | Description | Example |
|---|---|---|---|
enrichment_name | String | Name of the verification enrichment | "Verify OTP SMS" |
enrichment_endpoint | String | API endpoint used to verify the OTP | "https://service.socure.com/api/3.0/otp/verify" |
enrichment_provider | String | Provider name | "SocureOTP" |
status_code | Number | HTTP status code returned by provider | 200 |
request.otp | String | The OTP code submitted by user | "123456" |
request.verificationId | String | OTP session identifier used to validate | "aaa4703c-16b6-4a25-9c70-58722a26c7ed" |
response.attemptCount | Number | Number of verification attempts made | 1 |
response.status | String | Verification result returned by the OTP provider. Possible values: • "approved" — The user submitted the correct code within the session window.• "pending" — The code has been delivered but not yet verified.• "reject" — Verification failed. This occurs when the OTP session has expired (the 10-minute window elapsed) and the user submits a code after expiration, or when the maximum number of failed attempts (5) has been reached. Once a session expires, the underlying verification resource is destroyed by the provider, so any subsequent verification attempt against that session returns "reject". | "approved" |
response.verificationId | String | OTP session identifier returned by provider | "aaa4703c-16b6-4a25-9c70-58722a26c7ed" |
is_source_cache | Boolean | Whether data was cached instead of fetched live | false |
Security and implementation guidelines
Message Customization:
Configure OTP message templates—including custom branding, specific wording, and multi-language support—at the account level. You can't modify these dynamically via the
/api/evaluationpayload. To update your SMS, Voice, or Email templates, contact your Socure Account Manager or Support Team.
Input Validation & Security
Data Validation Requirements:
- Validate phone numbers in E.164 format before sending requests.
- Validate email addresses using proper regex patterns.
- Sanitize all user inputs to prevent injection attacks.
- Never log or store OTP codes in plain text or your database.
Security Implementation:
- Always use HTTPS for all API requests.
- Implement proper request/response logging with correlation IDs.
- Redact sensitive data (OTP codes, phone numbers) from logs.
- Use secure storage for API keys and credentials.
Error Handling & User Experience
Robust Error Handling:
- Implement retry logic for network timeouts and
5xxerrors. - Handle Rate Limits (HTTP 429): Implement exponential backoff based on these system thresholds:
- OTP Generation: 5 requests per minute per destination.
- OTP Verification: 10 requests per minute per
verificationId.
- Provide clear, actionable error messages to users based on the
sub_statusreturned. - Log errors with sufficient detail (including
eval_idandverificationId) for debugging.
Optimal User Experience:
- Display remaining attempts clearly to users. Note that requesting a Resend does not reset the 5-attempt limit.
- Show a countdown timer during the 10-minute cooling period if a lockout occurs.
- Provide resend functionality but notify the user that the original 10-minute session expiration still applies.
- Allow channel switching (e.g., SMS to Email) by using the
actions.resume: truesequence to clear the session before restarting. - Give immediate feedback on code entry to prevent users from accidentally double-submitting and wasting attempts.
Validation checklist
Testing requirements
Schema & API compliance
Security & monitoring
Updated 2 days ago

