Integration Guide
Learn how to integrate Socure's OTP service with a step-by-step guide covering sending OTPs, delivery, verification, and decision handling for authentication and fraud prevention workflows.
Socure One-Time Passcode API integration guide
This guide walks you through how to integrate with Socure’s /api/evaluation endpoint using the One-Time Passcode enrichment. You'll learn how to send phone number/email, deliver and validate One-Time Passcodes, and apply decision logic to support onboarding, authentication, fraud prevention, and compliance workflows.
Before you start
Make sure your RiskOS™ environment is provisioned with:
Postman Collection
The following Postman collection can be used to test the One-Time Passcode 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
Get an API key
- In the Sandbox RiskOS™ Dashboard, go to Developer Workbench > API Keys.
- Copy your API key securely.
How it works
The One-Time Passcode integration follows an asynchronous pattern. Unlike a standard identity check, the API will pause and wait 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 | Show "Session Expired" screen; prompt user to restart. |
One-Time Passcode implementation timeline
Initiate delivery (
POST)Submit the applicant's contact details to start the session. This triggers the initial One-Time Passocde delivery via SMS, Voice, or Email depending on your workflow configuration.
The "Wait" State: Because One-Time Passocde is an asynchronous process, RiskOS™ pauses the evaluation. You will receive a
REVIEWdecision and anON_HOLDstatus.
Key requirements:
- SMS/Voice One-Time Passocde: Include
phone_numberin E.164 format (e.g., +14155550100). - Email One-Time Passocde: Include
emailaddress in valid RFC-5322 format (e.g., [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]" } } }'Handle the "On-Hold" response
Your application must capture the
eval_idfrom the response. This ID is the unique "key" for the user's current session, which typically remains valid for 10 minutes.- 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" }- UI Action: When your frontend sees the
Manage "In-Between" UX logic
While the evaluation is
ON_HOLD, use theeval_idto handle non-linear user actions without breaking the session.Scenario: User didn't receive the code (Resend)
If the user requests a resend, do not initiate a new evaluation. Use a
PATCHrequest with theresendflag 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 } } } }'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_idcaptured 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 (e.g., 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" } } } }'Receive final decision
If the code is correct, RiskOS™ automatically resumes the workflow to perform final scoring. The session typically moves from
ON_HOLDto a terminalCLOSEDstatus.A successful One-Time Passcode verification typically results in an
ACCEPTdecision, 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
{ "id": "verify_attempt_001", "eval_id": "123e4567-e89b-12d3-a456-426614174000", "status": "CLOSED", "sub_status": "Approve", "decision": "ACCEPT", "tags": [ "OTP_VERIFIED", "Low Risk" ] }{ "id": "verify_attempt_001", "eval_id": "123e4567-e89b-12d3-a456-426614174000", "status": "ON_HOLD", "sub_status": "More information needed", "decision": "REVIEW", "tags": [ "OTP Approved", "Prefill Unsuccessful" ] }
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 (and potentially rate-limited) One-Time Passcode request.
Technical appendix: API reference
This section provides the exhaustive schema definitions for the /api/evaluation endpoint when used with the One-Time Passcode 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
Field | Type | Required | Description | Example |
|---|---|---|---|---|
| String | Required | 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. |
|
| String | Required | RFC 3339 timestamp indicating when the evaluation request was initiated by your system. |
|
| String | Required | Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > Developer Workbench > Integration Checklist. |
|
| Object | Required | Main payload containing consumer information, device data, and event details for evaluation. | |
→ | Object | Required | Primary identity object containing individual's information. | See |
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 with country code. E.164 format preferred for best results. | "+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
Field | Type | Required | Description | Example |
|---|---|---|---|---|
| String | Required | 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. |
|
| String | Required | RFC 3339 timestamp indicating when the evaluation request was initiated by your system. |
|
| String | Required | Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > Developer Workbench > Integration Checklist. |
|
| Object | Optional | A top-level container for instructions that modify the lifecycle of the current evaluation session. |
|
→ | Boolean | Required | Set to |
|
| Object | Required | Main payload containing consumer information, device data, and event details for evaluation. | |
→ | Object | Required | Primary identity object containing individual's information. | See |
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
The following fields are used 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"
}
}
]
}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 (e.g., One-Time Passcode 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: "approved", "pending", or "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:
One-Time Passcode message templates—including custom branding, specific wording, and multi-language support—are configured at the account level. These cannot be modified dynamically via the
/api/evaluationpayload. To update your SMS, Voice, or Email templates, please 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 easy 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 16 days ago
