Integrate One-Time Passcode (OTP)
Before you start
Make sure your RiskOS™ environment is provisioned with:
A workflow configured for the One-Time Passcode (OTP) enrichment.
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.
RiskOS™ClientAppUserRiskOS™ClientAppUserPauses evaluationopt[User requests Resend]opt[User switches Channel (e.g., SMS to Email)]alt[OTP Valid][OTP Invalid/Expired]Enter phone/emailPOST /evaluation200 OK (status: ON_HOLD, sub_status: Pending OTP Code)Deliver OTP (SMS/Email/Voice)Input 6-digit codePATCH /evaluation/{eval_id} (otp.code: "123456")200 OK (status: CLOSED, decision: ACCEPT)400 Bad Request (msg: "Invalid code")PATCH /evaluation/{eval_id} (otp.resend: true)Re-deliver same codePATCH /evaluation/{eval_id} (actions.resume: true)POST /evaluation (new email payload)Deliver new code to Email
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)
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, name@example.com).
Example request
cURL
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": "user@example.com"
}
}
}'
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
JSON
{
"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
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
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
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.
Step 1: Bypass the active SMS step.
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", and decision: "REJECT".
Example expired-session response
JSON
{
"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"
}
}
]
}
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
JSON
{
"status": 400,
"error": "Bad Request",
"message": "Max verify attempts reached"
}
Step 4: Verify the code (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
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.