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:

A workflow configured for the One-Time Passcode enrichment.

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 000000 during 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

  1. In the Sandbox RiskOS™ Dashboard, go to Developer Workbench > API Keys.
  2. 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.

  1. Initiate delivery: Send a POST /api/evaluation request with the user’s contact information (phone number and/or email).
  2. Wait State: RiskOS™ creates a verification session and delivers the 6-digit code via SMS, Voice, or Email. The API returns an ON_HOLD status and a unique eval_id.
  3. Verify and Resume: Once the user enters the code into your UI, submit a PATCH /api/evaluation/{eval_id} request to validate the code.
  4. Final Decision: RiskOS™ resumes the workflow, applies final scoring logic, and returns a terminal decision (ACCEPT, REJECT, or REVIEW).

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 Actionstatussub_statusdecisionYour Application Action
Initial POST (Success)ON_HOLDPending OTP CodeREVIEWTransition UI to the 6-digit code entry screen.
Enters correct OTP (KYC Complete)CLOSEDApproveACCEPTRedirect to Success/Onboarding Complete page.
Enters correct OTP (KYC Incomplete)ON_HOLDMore information neededREVIEWPrompt user for manual PII (Name, Address, SSN) and PATCH evaluation using eval_id.
Enters incorrect OTPON_HOLDPending OTP CodeREVIEWShow "Incorrect code" error; display remaining attempts.
5 failed attemptsCLOSEDMax attempts reachedREJECTDisable inputs; start 10-minute cooling period timer.
Requests OTP resendON_HOLDPending OTP CodeREVIEWNotify user that a new message was sent; keep UI on entry screen.
Switches channelON_HOLDPending OTP CodeREVIEWCall PATCH (resume: true) to clear the hold, then a new POST for the new channel.
Session expiresCLOSEDDeclineREJECTShow "Session Expired" screen; prompt user to restart.


One-Time Passcode implementation timeline

  1. 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 REVIEW decision and an ON_HOLD status.


    Key requirements:

    • SMS/Voice One-Time Passocde: Include phone_number in E.164 format (e.g., +14155550100).
    • Email One-Time Passocde: Include email address 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]"
        }
      }
    }'
  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, 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"
    }
  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.


    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 }
        }
      }
    }'
  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 (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" }
        }
      }
    }'
  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 One-Time Passcode 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" with sub_status: "More information needed", device possession is verified, but you must now PATCH the evaluation with the user's manual PII (Name, Address, SSN, etc.) to complete the identity profile.

    • Implementation note: If the evaluation results in a REJECT or REVIEW decision 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_statusstatusUX MeaningUI Action
Pending OTP CodeON_HOLDThe code has been delivered successfully.Transition from "Processing" to the 6-digit code entry screen.
Max attempts reachedCLOSEDSecurity lockout due to 5 failed attempts.Disable inputs and show a 10-minute countdown timer.
ApproveCLOSEDThe code was correct and the user is low risk.Redirect to your Success/Onboarding Complete page.
DeclineCLOSEDThe session expired or the user is high risk.Show a "Verification Failed" screen with an option to restart.

Handling the eval_id

Your 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_id from 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)

Use these fields to start a new verification session.

Initiation delivery

Endpoint

POST https://riskos.sandbox.socure.com/api/evaluation
POST https://riskos.socure.com/api/evaluation

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 version

Example 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

id

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.

"a86580cc-1733-4188-86b5-717166e1db8c"

timestamp

String <Date-Time>

Required

RFC 3339 timestamp indicating when the evaluation request was initiated by your system.

"2025-05-02T12:44:22.059Z"

workflow

String

Required

Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > Developer Workbench > Integration Checklist.

"consumer_onboarding"

data

Object

Required

Main payload containing consumer information, device data, and event details for evaluation.

individual

Object

Required

Primary identity object containing individual's information.

See individual schema below.


individual fields
FieldTypeRequiredDescriptionExample
emailStringRequiredConsumer's email address (must be a valid email format)"[email protected]"
phone_numberStringRequiredConsumer'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)

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.

FieldTypeDescriptionExample
enrichment_nameStringName of the delivery enrichment."Deliver OTP SMS"
enrichment_endpointStringAPI endpoint used to trigger the delivery."https://service.socure.com/api/3.0/otp/send"
enrichment_providerStringProvider handling the delivery."SocureOTP"
status_codeNumberHTTP status from the provider (200 = Success).200
request.destinationStringThe phone number or email where the code was sent."+14155550100"
response.verificationIdStringThe unique ID for the delivery attempt (Required for Support)."aaa4703c-16b6-4a25-9c70-58722a26c7ed"
is_source_cacheBooleanWhether data was cached (Always false for OTP).false

2. Management and verification schema (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.

  1. Send a PATCH with "actions": { "resume": true }.
  2. Once the session is resumed, send a new POST /api/evaluation with 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 version

Example 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

id

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.

"a86580cc-1733-4188-86b5-717166e1db8c"

timestamp

String <Date-Time>

Required

RFC 3339 timestamp indicating when the evaluation request was initiated by your system.

"2025-05-02T12:44:22.059Z"

workflow

String

Required

Your environment-specific workflow identifier. You can find this value in the RiskOS™ Dashboard > Developer Workbench > Integration Checklist.

"consumer_onboarding"

actions

Object

Optional

A top-level container for instructions that modify the lifecycle of the current evaluation session.

{"actions": {...}}

resume

Boolean

Required

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.

true

data

Object

Required

Main payload containing consumer information, device data, and event details for evaluation.

individual

Object

Required

Primary identity object containing individual's information.

See individual schema below.


individual.otp fields

FieldTypeActionDescription
codeStringVerifyThe 6-digit code entered by the user.
resendBooleanResendSet 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
FieldTypeDescriptionExample
idStringUnique evaluation identifier provided in your request."OTP-Test"
workflowStringRiskOS™ workflow name used for the evaluation."individual_onboarding"
workflow_idStringThe unique UUID of the specific workflow definition used."2049b724-0461-4a4b-8526-6c26a0c1e88a"
workflow_versionStringThe specific version of the workflow logic applied to this session."8.13.0"
eval_sourceStringOrigin of the evaluation invocation (e.g., "API")."API"
eval_start_timeStringRFC 3339 timestamp indicating when the evaluation session began."2025-09-30T10:00:00Z"
eval_end_timeStringRFC 3339 timestamp indicating when the evaluation ended or paused."2025-09-30T10:00:03Z"
decisionString (enum)Final evaluation result.

Possible values:
ACCEPT
REVIEW
REJECT

Note: The fields returned can be customized to fit your integration or business needs.
"REJECT"
decision_atString (RFC 3339)Timestamp of the final decision."2025-09-26T21:31:27.662218159Z"
statusString (enum)Case-level status of the evaluation.

Possible values:
OPEN
CLOSED
"CLOSED"
sub_statusStringProvides additional detail about the evaluation status.

Example values:
Under Review
Pending Verification
Accept
Reject
"Accept"
tagsArray of StringsDescriptive labels summarizing the decision context.["OTP Unsuccessful"]
review_queuesArray of StringsQueues used for routing manual reviews.["Default Queue"]
data_enrichmentsArray of ObjectsList of enrichment calls (e.g., One-Time Passcode send/verify attempts), including request/response details and provider metadata.
eval_statusString (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_nameStringIndicates which environment the evaluation ran in. Typically Sandbox for testing or Production for live traffic."Sandbox"

data_enrichments fields - OTP Verification
FieldTypeDescriptionExample
enrichment_nameStringName of the verification enrichment"Verify OTP SMS"
enrichment_endpointStringAPI endpoint used to verify the OTP"https://service.socure.com/api/3.0/otp/verify"
enrichment_providerStringProvider name"SocureOTP"
status_codeNumberHTTP status code returned by provider200
request.otpStringThe OTP code submitted by user"123456"
request.verificationIdStringOTP session identifier used to validate"aaa4703c-16b6-4a25-9c70-58722a26c7ed"
response.attemptCountNumberNumber of verification attempts made1
response.statusStringVerification result: "approved", "pending", or "reject""approved"
response.verificationIdStringOTP session identifier returned by provider"aaa4703c-16b6-4a25-9c70-58722a26c7ed"
is_source_cacheBooleanWhether data was cached instead of fetched livefalse


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/evaluation payload. 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 5xx errors.
  • 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_status returned.
  • Log errors with sufficient detail (including eval_id and verificationId) 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: true sequence 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

Test successful OTP verification → ACCEPT decision
Test invalid/expired OTPs → REJECT decision
Test max attempts reached → proper cooling period behavior
Test resend functionality works correctly
Test channel switching (SMS ↔ Email) if enabled
Load test with expected traffic volumes

Schema & API compliance

Request payloads match OpenAPI schema exactly
Error responses return structured error codes
Phone numbers formatted in correct E.164 format
Timestamps in proper RFC 3339 format

Security & monitoring

API keys securely stored and rotated regularly
All requests logged with correlation IDs for tracing
Sensitive data (OTP codes) redacted from all logs
Rate limiting implemented on your application layer
Monitoring alerts configured for high failure rates