@power-rent/phone-validation-adapter
v1.0.0
Published
REST API for phone number validation using Twilio Lookup API
Downloads
43
Readme
Phone Validation Adapter
REST API adapter for phone number validation using Twilio Lookup API. This project was created to solve a critical issue: legacy applications running on Node.js < 14 cannot directly install the Twilio SDK due to version incompatibility. This adapter provides a simple REST interface that allows legacy systems to validate phone numbers by calling this service, eliminating the need to upgrade their entire Node.js infrastructure.
Setup
Prerequisites
- Node.js >= 18.0.0
- Twilio Account with API credentials
Installation
npm installEnvironment Variables
Create a .env file in the root directory:
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
PORT=3000Important: The TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN are required. Without them, the validation service will return HTTP 503.
API Key Configuration
All requests to the validation endpoint require an API key and project name for authentication. API keys are stored in src/validApiKeys.ts:
const validApiKeys: Record<string, string> = {
RLC: process.env.API_KEY_RLC || '',
TJS: process.env.API_KEY_TJS || ''
};Project Names:
RLC- Power Rent Rental CompanyTJS- TJ Services
To add a new project:
- Add environment variable to
.env:
API_KEY_NEW_PROJECT="your-secret-api-key"- Update
src/validApiKeys.ts:
const validApiKeys: Record<string, string> = {
RLC: process.env.API_KEY_RLC || '',
TJS: process.env.API_KEY_TJS || '',
NEW_PROJECT: process.env.API_KEY_NEW_PROJECT || ''
};- Rebuild and restart:
npm run build
npm run startImportant Security Notes:
- Never commit actual API keys to version control
- Store keys in environment variables or a secrets manager
- Each client application should have its own unique key
- Rotate keys regularly (every 90 days recommended)
API Endpoints
POST /validate
Validates a phone number using Twilio Lookup API.
Authentication Required: x-api-key and x-project-name headers with valid credentials.
Request
curl -X POST http://localhost:3000/validate \
-H "Content-Type: application/json" \
-H "x-api-key: apple-blossom-elephant-rocket" \
-H "x-project-name: RLC" \
-d '{"phoneNumber": "+1234567890"}'Request body:
{
"phoneNumber": "+1234567890"
}Request headers:
| Header | Value | Required | Description |
|--------|-------|----------|-------------|
| x-api-key | API Key | Yes | Project-specific API key for authentication |
| x-project-name | Project Name | Yes | Project identifier (RLC or TJS) |
| Content-Type | application/json | Yes | Request content type |
Response (Success - HTTP 200)
{
"success": true,
"isValid": true
}Response (Invalid Number - HTTP 200)
{
"success": true,
"isValid": false
}Response (Unauthorized - HTTP 401)
Returned when API key or project name is missing or invalid:
{
"success": false,
"error": "Invalid API key"
}Or:
{
"success": false,
"error": "Invalid project name"
}Response (Client Error - HTTP 400)
{
"success": false,
"error": "phoneNumber is required and must be a non-empty string"
}Response (Service Unavailable - HTTP 503)
Returned when Twilio credentials are not configured:
{
"success": false,
"error": "Validation service is temporarily unavailable"
}Response (Server Error - HTTP 500)
Returned when Twilio API fails or times out:
{
"success": false,
"error": "Phone validation failed. Please try again later."
}Error Handling Flow
This diagram shows how errors are handled at different stages:
POST /validate
│
├─ API Key Validation
│ ├─ x-project-name header missing?
│ │ └─ HTTP 401 (Unauthorized)
│ │ └─ Client error: invalid project name
│ │
│ ├─ x-project-name exists in whitelist?
│ │ └─ Continue to x-api-key validation
│ │
│ ├─ x-api-key header missing?
│ │ └─ HTTP 401 (Unauthorized)
│ │ └─ Client error: invalid API key
│ │
│ └─ x-api-key matches project?
│ └─ Continue to Input validation
│
├─ Input Validation
│ ├─ Missing or empty phoneNumber?
│ │ └─ HTTP 400 (Bad Request)
│ │ └─ Client error: invalid input
│ │
│ └─ phoneNumber is valid string?
│ └─ Continue to Twilio validation
│
├─ Configuration Check (twilioLookupValidation.ts)
│ ├─ TWILIO_ACCOUNT_SID missing?
│ │ ├─ Throw ConfigurationError
│ │ ├─ Log to Sentry
│ │ └─ server.ts catches it
│ │ └─ HTTP 503 (Service Unavailable)
│ │ └─ User sees: "Service temporarily unavailable"
│ │
│ ├─ TWILIO_AUTH_TOKEN missing?
│ │ ├─ Throw ConfigurationError
│ │ ├─ Log to Sentry
│ │ └─ server.ts catches it
│ │ └─ HTTP 503 (Service Unavailable)
│ │
│ └─ Credentials OK?
│ └─ Call Twilio API with 5s timeout
│
├─ Twilio API Call
│ ├─ Request times out (> 5s)?
│ │ ├─ Throw ValidationError
│ │ ├─ Log to Sentry
│ │ └─ server.ts catches it
│ │ └─ HTTP 500 (Internal Server Error)
│ │ └─ User sees: "Validation failed, try again"
│ │
│ ├─ Twilio API returns error?
│ │ ├─ Throw ValidationError
│ │ ├─ Log to Sentry
│ │ └─ server.ts catches it
│ │ └─ HTTP 500 (Internal Server Error)
│ │
│ ├─ Phone is valid?
│ │ └─ Return true
│ │ └─ HTTP 200 + isValid: true
│ │ └─ User can submit form
│ │
│ └─ Phone is invalid?
│ └─ Return false
│ └─ HTTP 200 + isValid: false
│ └─ User sees validation error message
│
└─ Error Logging (All paths)
└─ Sentry with error type tag
├─ ConfigurationError → errorType: 'configuration'
└─ ValidationError → errorType: 'validation'Key Design Decisions
1. Explicit Error Types
Two custom error classes distinguish between different failure modes:
- ConfigurationError: Missing Twilio credentials (HTTP 503)
- ValidationError: Twilio API failures or timeouts (HTTP 500)
This prevents silent failures where configuration problems would be masked as valid numbers.
2. Timeout Protection
Twilio API calls have a 5-second timeout. If exceeded:
- ValidationError is thrown
- HTTP 500 is returned
- User is informed of the failure
3. No Silent Failures
Previous implementation returned true for missing credentials. This caused:
- Users to believe their phone was validated
- Form submissions with invalid phone numbers
- Data quality issues
Current implementation:
- Fails explicitly with HTTP 503
- Informs users the service is unavailable
- Prevents invalid data submission
Architecture & Security
This adapter is designed as a backend microservice that bridges the gap between legacy applications and Twilio. Understanding the architecture is critical for proper deployment and security.
Client-Server Architecture
┌─────────────────────────────────┐
│ Client Application │
│ (Frontend/Backend/Legacy app) │
└────────────┬────────────────────┘
│ HTTP POST /validate
│ with x-api-key header
↓
┌─────────────────────────────────────────────┐
│ Phone Validation Adapter │
│ (this service) │
│ ✓ Validates API key │
│ ✓ Validates phone number format │
│ ✓ Calls Twilio API │
│ ✓ Returns validation result │
└────────────┬────────────────────────────────┘
│
↓
┌───────────────────┐
│ Twilio API │
└───────────────────┘Backend Responsibilities
The backend service (this adapter) is responsible for:
Store API Keys Securely
API keys should never be hardcoded in production. Instead, use one of these approaches:
Environment Variables (recommended for simple deployments):
VALID_API_KEYS=key1,key2,key3Secrets Manager (AWS Secrets Manager, HashiCorp Vault, etc.):
// Pseudo-code const keys = await secretsManager.getSecret('phone-validation-keys');Database (for dynamic key management):
// Pseudo-code const keys = await database.query('SELECT api_key FROM valid_keys WHERE active = true');
Issue Keys to Clients
Provide a secure endpoint where authenticated clients can request API keys:
// Pseudo-code example app.post('/api-keys/request', (req, res) => { // Verify client identity (JWT, OAuth, mTLS, etc.) // Generate or retrieve API key // Return key to client res.json({ apiKey: 'generated-key' }); });Important: This endpoint should:
- Require strong authentication (JWT, OAuth, mTLS)
- Rate limit requests
- Log all key requests
- Expire keys automatically
- Allow key rotation
Validate Keys Before Processing
Always verify API keys on the server before calling Twilio:
// Current implementation does this via middleware const authenticateApiKey = (req, res, next) => { const apiKey = req.headers['x-api-key']; const projectName = req.headers['x-project-name']; if (!checkIfKeyExists(projectName)) { res.status(401).json({ error: 'Invalid project name' }); return; } if (!isValidApiKey({ key: apiKey, projectName })) { res.status(401).json({ error: 'Invalid API key' }); return; } next(); };Benefits:
- Keys never exposed to clients
- Project-specific key validation
- Prevents unauthorized Twilio API calls
- Reduces costs (Twilio charges per request)
- Enables per-project rate limiting
- Audit trail for all validation attempts
Frontend/Client Responsibilities
Client applications should:
- Never hardcode API keys in frontend code
- Request keys from backend via secure endpoint
- Pass keys in request headers (
x-api-key) - Handle 401 responses gracefully
- Implement retry logic for transient failures
Example client usage:
// Get API key from backend (must be authenticated)
const apiKey = await fetch('/api/phone-validation-key', {
headers: { Authorization: 'Bearer ' + userToken }
}).then(r => r.json()).then(r => r.apiKey);
// Get project name from backend config or user settings
const projectName = 'RLC'; // or 'TJS'
// Use key to validate phone
const result = await fetch('https://validator-service.example.com/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'x-project-name': projectName
},
body: JSON.stringify({ phoneNumber: '+1234567890' })
});Security Best Practices
- Rotate API Keys Regularly - Change keys every 90 days
- Use HTTPS - All communication must be encrypted
- Rate Limit - Prevent abuse of the validation endpoint
- Monitor Usage - Track validation requests and costs
- Separate Credentials - Twilio keys ≠ Adapter keys
- Audit Logging - Log all API key usage
- Principle of Least Privilege - Give clients minimum permissions needed
Development
Start Development Server
npm run devServer runs on http://localhost:3000 by default.
Build
npm run buildOutput goes to dist/ directory.
Testing
npm run lint
npm run format:checkLicense
MIT
