OTP Verification
The OTP Verification integration verifies consumer phone ownership by sending a 6-digit one-time passcode via SMS. The consumer enters the code on your form, and eConsent confirms the match, providing cryptographic proof that the phone number belongs to the person who submitted consent.
| Detail | Value |
|---|---|
| Cost | $0.10 per verification |
| Rate limit | 300 requests/min per company |
| Cooldown | 2 minutes per phone number |
| Billing | Wallet-based (pre-paid) |
How it works
Section titled “How it works”OTP verification uses a two-step flow:
- Start. Your application sends the consumer’s phone number to the start endpoint. eConsent sends a 6-digit code via SMS.
- Complete. The consumer enters the code on your form. Your application sends the code to the complete endpoint for verification.
Quick start
Section titled “Quick start”- Go to Integrations in your dashboard at app.econsent.org.
- Enable OTP Verification and copy your API token.
- Send a verification code:
curl -X POST https://api.econsent.org/api/integrations/YOUR_COMPANY_ID/otp/start \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "phone_number": "5551234567", "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }'Endpoints
Section titled “Endpoints”Start verification
Section titled “Start verification”Sends a 6-digit OTP code to the consumer’s phone via SMS.
POST https://api.econsent.org/api/integrations/:companyId/otp/startPath parameters
Section titled “Path parameters”| Parameter | Type | Description |
|---|---|---|
companyId | string | Your Company ID |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | Consumer’s phone number (digits only, no country code prefix required) |
session_id | string | Yes | The eConsent session ID to associate this verification with |
{ "phone_number": "5551234567", "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}Success response (200)
Section titled “Success response (200)”The response includes carrier information detected from the phone number, along with billing details showing the charge deducted from your wallet.
{ "success": true, "status": "code_sent", "carrier_name": "T-Mobile", "carrier_type": "WIRELESS", "phone": "5551234567", "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "message": "Verification code sent", "billing": { "cost_charged": 0.10, "remaining_balance": 99.90 }, "processing_time_ms": 450}Complete verification
Section titled “Complete verification”Verifies the code entered by the consumer against the pending OTP.
POST https://api.econsent.org/api/integrations/:companyId/otp/completePath parameters
Section titled “Path parameters”| Parameter | Type | Description |
|---|---|---|
companyId | string | Your Company ID |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | The same phone number used in the start request |
code | string | Yes | The 6-digit code entered by the consumer |
session_id | string | Yes | The eConsent session ID from the start request |
{ "phone_number": "5551234567", "code": "123456", "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}Success response (200)
Section titled “Success response (200)”When the code matches, the response includes identity metadata associated with the phone number. If a session_id was provided, the verification result is automatically stored on that session for inclusion in the consent certificate.
{ "success": true, "status": "verified", "phone": "5551234567", "verified_at": "2026-04-02T14:31:12Z", "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "identity_meta": { "first_name": "Sarah", "last_name": "Mitchell", "city": "Richmond", "state": "VA" }}Failure response
Section titled “Failure response”If no pending verification exists (expired or never started), the response indicates the failure:
{ "success": false, "message": "No pending verification for this phone number."}Authentication
Section titled “Authentication”Use Bearer token authentication with your OTP Verification integration token:
Authorization: Bearer YOUR_API_TOKENRate limits
Section titled “Rate limits”| Limit | Value | Description |
|---|---|---|
| Per company | 300 requests/min | Applies across all OTP requests (start + complete) |
| Per phone number | 1 request per 2 minutes | Cooldown on start requests to prevent SMS spam |
Certificate integration
Section titled “Certificate integration”When OTP verification completes successfully, the result is automatically attached to the consent certificate’s Verification Dossier. The dossier includes:
| Field | Description |
|---|---|
| Phone number | The verified phone number |
| Verification status | verified or failed |
| Verified at | Timestamp of successful verification |
| Session ID | The session the verification is linked to |
This provides tamper-proof evidence that the consumer who submitted consent also had physical access to the phone number at the time of consent capture.
Error responses
Section titled “Error responses”| Status | Error | Description |
|---|---|---|
400 | invalid_phone_number | Phone number format is invalid or missing |
400 | invalid_code | The code does not match the pending verification |
401 | unauthorized | Invalid or missing API token |
402 | insufficient_balance | Wallet balance too low to cover the $0.10 charge |
404 | verification_not_found | No pending verification exists for this phone number and session |
410 | code_expired | The verification code has expired (codes are valid for 5 minutes) |
429 | rate_limited | Exceeded 300 requests/min or 2-minute per-number cooldown |
Error response example
Section titled “Error response example”{ "success": false, "status": 410, "error": "code_expired", "message": "The verification code has expired. Please request a new code.", "company_id": "comp-abc", "timestamp": "2026-04-02T14:38:00Z"}Code examples
Section titled “Code examples”const companyId = process.env.ECONSENT_COMPANY_ID;const token = process.env.ECONSENT_OTP_TOKEN;const baseUrl = `https://api.econsent.org/api/integrations/${companyId}/otp`;
async function startOtp(phoneNumber, sessionId) { const response = await fetch(`${baseUrl}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ phone_number: phoneNumber, session_id: sessionId, }), });
const data = await response.json();
if (!data.success) { throw new Error(data.message); }
return data;}
async function completeOtp(phoneNumber, code, sessionId) { const response = await fetch(`${baseUrl}/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ phone_number: phoneNumber, code: code, session_id: sessionId, }), });
const data = await response.json();
if (!data.success) { throw new Error(data.message); }
console.log('Verified at:', data.verified_at); return data;}import osimport requests
COMPANY_ID = os.getenv('ECONSENT_COMPANY_ID')TOKEN = os.getenv('ECONSENT_OTP_TOKEN')BASE_URL = f'https://api.econsent.org/api/integrations/{COMPANY_ID}/otp'
def start_otp(phone_number, session_id): response = requests.post( f'{BASE_URL}/start', headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {TOKEN}', }, json={ 'phone_number': phone_number, 'session_id': session_id, }, )
data = response.json()
if not data.get('success'): raise Exception(data.get('message'))
return data
def complete_otp(phone_number, code, session_id): response = requests.post( f'{BASE_URL}/complete', headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {TOKEN}', }, json={ 'phone_number': phone_number, 'code': code, 'session_id': session_id, }, )
data = response.json()
if not data.get('success'): raise Exception(data.get('message'))
print(f"Verified at: {data['verified_at']}") return data<?php$companyId = getenv('ECONSENT_COMPANY_ID');$token = getenv('ECONSENT_OTP_TOKEN');$baseUrl = "https://api.econsent.org/api/integrations/$companyId/otp";
function startOtp($phoneNumber, $sessionId) { global $baseUrl, $token;
$ch = curl_init("$baseUrl/start"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'phone_number' => $phoneNumber, 'session_id' => $sessionId, ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', "Authorization: Bearer $token", ]);
$response = curl_exec($ch); curl_close($ch);
$result = json_decode($response, true);
if (!$result['success']) { throw new Exception($result['message']); }
return $result;}
function completeOtp($phoneNumber, $code, $sessionId) { global $baseUrl, $token;
$ch = curl_init("$baseUrl/complete"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'phone_number' => $phoneNumber, 'code' => $code, 'session_id' => $sessionId, ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', "Authorization: Bearer $token", ]);
$response = curl_exec($ch); curl_close($ch);
$result = json_decode($response, true);
if (!$result['success']) { throw new Exception($result['message']); }
echo "Verified at: " . $result['verified_at'] . "\n"; return $result;}?>Best practices
Section titled “Best practices”- Link to session. Always pass the
session_idso the verification result is attached to the correct consent certificate. - Handle expiration gracefully. Codes expire after 5 minutes. Display a “Resend code” button that respects the 2-minute cooldown.
- Validate phone first. Use Phone Validation before sending OTP to confirm the number is a reachable mobile line. This avoids wasting $0.10 on undeliverable messages.
- Client-side UX. Show a countdown timer for the 2-minute cooldown and auto-focus the code input field after sending.
- Do not retry on invalid code. If the consumer enters the wrong code, let them re-enter it. Do not automatically trigger a new start request.
Pricing
Section titled “Pricing”- Cost per verification: $0.10 (charged on start)
- Rate limit: 300 requests/min per company
- Cooldown: 2 minutes per phone number
- Billing method: Wallet-based (pre-paid via Stripe)
Next steps
Section titled “Next steps”- Phone Validation. Validate phone numbers before sending OTP
- Email Validation. Validate email addresses at $0.05/check
- Certificate Overview. Understand how verification results appear on certificates
- Pricing & Billing. Full pricing breakdown