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 | 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
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. By default, the OTP will remain active for 10 minutes. If the user does not authenticate with that OTP within that timeframe, you will need to execute a new OTP request in the workflow and get a neweval_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" }- 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 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
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 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:
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 about 9 hours ago
