Skip to main content

Overview

Verify a one-time password (OTP) code that was previously sent via SMS. This endpoint validates the code against the request ID returned from the Initiate OTP endpoint.

Endpoint

curl --request POST \
  "https://sms.lamah.com/api/otp/verify" \
  --header "Authorization: Bearer YOUR_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "request_id": "8c8a6f2f-9a3b-4d86-9b2c-1e3f8f9c2ab1",
    "code": "123456"
  }'

Request Parameters

ParameterTypeRequiredDescription
request_idstring (UUID)The request ID returned from the initiate OTP endpoint
codestringThe OTP code entered by the user

Parameter Details

The unique identifier returned when the OTP was initiated.
  • Format: UUID (e.g., 8c8a6f2f-9a3b-4d86-9b2c-1e3f8f9c2ab1)
  • Source: Returned from the Initiate OTP endpoint
  • Validity: Must correspond to an active, non-expired OTP request
The OTP code that the user received via SMS.
  • Format: Numeric string (4 or 6 digits)
  • Case sensitivity: Not applicable (numeric only)
  • Validation: Must match the generated code exactly

Response

Success Response (200 OK)

{ "message": "OTP verified successfully" }

Response Fields

FieldTypeDescription
messagestringSuccess message
phone_numberstringThe phone number associated with this OTP
verified_atstringISO 8601 timestamp when verification occurred
attempts_usedintegerNumber of verification attempts used
max_attemptsintegerMaximum allowed verification attempts

Error Responses

400 Bad Request - Invalid Code

{ "message": "Invalid OTP" }

400 Bad Request - Expired OTP

{ "message": "OTP has expired" }

400 Bad Request - Too Many Attempts

{ "message": "OTP already verified" }

404 Not Found - Invalid Request ID

{ "message": "OTP not found" }

401 Unauthorized

{ "message": "Unauthenticated." }

403 Forbidden

{ "message": "Forbidden" }

Verification Rules

Attempt Limits

  • Maximum attempts: 3 verification attempts per OTP request
  • Lockout: After 3 failed attempts, the OTP becomes invalid
  • New request required: Must initiate a new OTP after lockout

Expiration

  • Time-based: OTP expires based on the expiration time set during initiation
  • Single use: Once successfully verified, the OTP cannot be used again
  • Automatic cleanup: Expired OTPs are automatically cleaned up

Implementation Example

class OTPService {
  constructor(apiToken) {
    this.apiToken = apiToken;
    this.baseUrl = 'https://sms.lamah.com/api';
  }

  async verifyOTP(requestId, code) {
    try {
      const response = await fetch(`${this.baseUrl}/otp/verify`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          request_id: requestId,
          code: code
        })
      });

      const result = await response.json();

      if (!response.ok) {
        console.log(`Verification failed: ${result.message}`);
        return { success: false, error: result.message };
      }

      console.log('OTP verified successfully!');
      this.clearStoredOTPRequest();
      return { success: true, data: result };
      
    } catch (error) {
      console.error('OTP verification error:', error);
      return { success: false, error: error.message };
    }
  }

  clearStoredOTPRequest() {
    // Clean up stored OTP data (implement per your app)
    localStorage.removeItem('otp_request');
  }

  getStoredOTPRequest(phoneNumber) {
    const stored = localStorage.getItem(`otp_${phoneNumber}`);
    return stored ? JSON.parse(stored) : null;
  }
}

// Usage example with UI integration
const otpService = new OTPService('YOUR_API_TOKEN');

const handleOTPVerification = async (phoneNumber, userEnteredCode) => {
  // Get the stored request ID
  const storedOTP = otpService.getStoredOTPRequest(phoneNumber);
  
  if (!storedOTP) {
    console.error('No OTP request found for this phone number');
    return false;
  }

  // Check if OTP has expired locally (optional client-side check)
  const now = new Date();
  const expiresAt = new Date(storedOTP.expiresAt);
  
  if (now > expiresAt) {
    console.log('OTP has expired locally');
    otpService.clearStoredOTPRequest(phoneNumber);
    return false;
  }

  // Verify the OTP
  const result = await otpService.verifyOTP(storedOTP.requestId, userEnteredCode);
  
  if (result.success) {
    console.log('User verified successfully!');
    // Proceed with your application logic
    return true;
  } else {
    console.log('Verification failed:', result.error);
    return false;
  }
};

Security Considerations

Rate Limiting

Implement client-side delays between verification attempts

Secure Storage

Never log or store the actual OTP codes

Attempt Tracking

Monitor failed attempts for potential abuse

Cleanup

Clear OTP data after successful verification

Best Practices

Client-Side Validation

const validateOTPCode = (code, expectedLength) => {
  // Check if code is numeric and correct length
  const isNumeric = /^\d+$/.test(code);
  const isCorrectLength = code.length === expectedLength;
  
  return isNumeric && isCorrectLength;
};

Error Handling Strategy

  1. Invalid Code: Allow retry with remaining attempts
  2. Expired OTP: Prompt user to request new OTP
  3. Max Attempts: Force new OTP request
  4. Network Error: Allow retry with same code

User Experience Tips

  • Show remaining attempts to users
  • Display expiration countdown
  • Provide clear error messages
  • Auto-submit when code length is reached

Common Integration Patterns

Form Validation

const otpForm = document.getElementById('otp-form');
const otpInput = document.getElementById('otp-code');
const errorDiv = document.getElementById('error-message');

otpForm.addEventListener('submit', async (e) => {
  e.preventDefault();
  
  const code = otpInput.value.trim();
  const requestId = getStoredRequestId(); // Your implementation
  
  if (!validateOTPCode(code, 6)) {
    errorDiv.textContent = 'Please enter a valid 6-digit code';
    return;
  }
  
  const result = await otpService.verifyOTP(requestId, code);
  
  if (result.success) {
    // Redirect to success page or continue flow
    window.location.href = '/dashboard';
  } else {
    errorDiv.textContent = result.error.error || 'Verification failed';
  }
});

Auto-Submit on Complete

otpInput.addEventListener('input', async (e) => {
  const code = e.target.value;
  
  if (code.length === 6 && validateOTPCode(code, 6)) {
    // Auto-submit when 6 digits are entered
    const requestId = getStoredRequestId();
    const result = await otpService.verifyOTP(requestId, code);
    
    if (result.success) {
      // Handle success
    } else {
      // Handle error and clear input
      otpInput.value = '';
      errorDiv.textContent = result.error.error;
    }
  }
});