Twilio Regulatory Compliance Provider
🎯 Overview
The Regulatory Compliance Provider (Providers/regulatory-compliance.js) manages regulatory requirements for phone number provisioning in countries that require identity verification and documentation.
Source: external/Integrations/Twilio/Providers/regulatory-compliance.js
Key Capabilities:
- Fetch regulatory requirements by country/number type
- Get list of countries with regulatory requirements
- Cache regulation data for performance (24-hour TTL)
- Support for different end user types (individual, business)
- Parse and structure complex regulation responses
Why Regulatory Compliance?
Many countries require identity verification before purchasing phone numbers:
- United States: A2P 10DLC registration for SMS
- United Kingdom: Address verification for all numbers
- Germany: Business registration documentation
- Australia: Identity verification for local numbers
🔌 Provider Functions
getRegulationRequirements()
Fetch regulatory requirements for phone numbers in a specific country.
Signature:
const getRegulationRequirements = async(
isoCountry,
(numberType = 'local'),
(endUserType = 'business'),
(twilioCredentials = null),
);
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
isoCountry | String | ✅ | - | ISO 3166-1 alpha-2 country code (GB, US, DE, AU, etc.) |
numberType | String | ❌ | 'local' | Phone number type: 'local', 'mobile', 'toll-free' |
endUserType | String | ❌ | 'business' | End user type: 'individual', 'business' |
twilioCredentials | Object | ❌ | null | Optional credentials: {accountSid, authToken} |
Returns:
Promise<{
hasRequirements: boolean; // Whether country has requirements
regulationSid?: string; // Regulation SID (if requirements exist)
friendlyName?: string; // Regulation name
isoCountry?: string; // Country code
numberType?: string; // Number type
endUserType?: string; // End user type
requirements?: {
address: Array<{
// Address requirements
constraint: string;
fields: string[]; // Required fields
detailed_fields: Array<{
machine_name: string; // Field name (e.g., 'street')
friendly_name: string; // Display name (e.g., 'Street')
description: string;
constraint: string;
}>;
name: string;
requirement_name: string;
type: string;
}>;
end_user: Array<{
// End user information requirements
constraint: string;
fields: string[];
name: string;
requirement_name: string;
type: string;
}>;
supporting_document: Array<{
// Supporting documents required
accepted_documents: string[];
constraint: string;
name: string;
requirement_name: string;
type: string;
}>;
};
message?: string; // Message if no requirements
}>;
Business Logic:
-
Check Cache First
- Caches results for 24 hours (production only)
- Cache key:
${isoCountry}_${numberType}_${endUserType} - Improves performance and reduces API calls
-
Initialize Twilio Client
- Uses provided credentials OR environment defaults
- Logs which credentials are being used
-
Fetch Regulations from Twilio
- Calls
client.numbers.v2.regulatoryCompliance.regulations.list() - Includes
includeConstraints: truefor detailed field info
- Calls
-
Parse and Structure Response
- Determines if requirements exist
- Structures nested requirement objects
- Adds standard address fields
-
Cache and Return
- Stores in cache with timestamp
- Returns structured requirements
Example Usage:
const complianceProvider = require('./Providers/regulatory-compliance');
// Check UK requirements for local numbers
const ukRequirements = await complianceProvider.getRegulationRequirements(
'GB',
'local',
'business',
);
console.log('Has requirements:', ukRequirements.hasRequirements); // true
console.log('Regulation:', ukRequirements.friendlyName); // "UK Local - Business"
if (ukRequirements.hasRequirements) {
// Check what's required
console.log('Address required:', ukRequirements.requirements.address.length > 0);
console.log('End user info required:', ukRequirements.requirements.end_user.length > 0);
console.log('Documents required:', ukRequirements.requirements.supporting_document.length > 0);
// Get address fields
if (ukRequirements.requirements.address.length > 0) {
const addressReq = ukRequirements.requirements.address[0];
console.log('Required address fields:', addressReq.fields);
// Output: ['customer_name', 'street', 'city', 'region', 'postal_code', 'iso_country']
}
}
Check US A2P Requirements:
// US requires A2P registration for SMS
const usRequirements = await complianceProvider.getRegulationRequirements(
'US',
'local',
'business',
);
console.log('US has requirements:', usRequirements.hasRequirements); // true
console.log('Required documents:', usRequirements.requirements.supporting_document);
// May require business registration, tax ID, etc.
Country with No Requirements:
// Some countries have no requirements
const caRequirements = await complianceProvider.getRegulationRequirements(
'CA',
'local',
'business',
);
console.log('Has requirements:', caRequirements.hasRequirements); // false
console.log('Message:', caRequirements.message);
// Output: "No regulatory requirements found for this country/number type combination"
Using Custom Credentials:
// Use subaccount credentials
const requirements = await complianceProvider.getRegulationRequirements('GB', 'local', 'business', {
accountSid: 'AC1234567890abcdef',
authToken: 'your_auth_token',
});
Data Flow:
sequenceDiagram
participant API as DashClicks API
participant Provider as Compliance Provider
participant Cache as In-Memory Cache
participant Twilio as Twilio Regulations API
API->>Provider: getRegulationRequirements(GB, local, business)
Provider->>Cache: Check cache(GB_local_business)
alt Cache hit (< 24 hours)
Cache-->>Provider: Return cached data
Provider-->>API: Return requirements
else Cache miss or expired
Provider->>Twilio: regulations.list()
Note over Twilio: includeConstraints: true
Twilio-->>Provider: Regulation data
Provider->>Provider: Parse response
Provider->>Cache: Store in cache (24h TTL)
Provider-->>API: Return requirements
end
style Provider fill:#e3f2fd
style Cache fill:#e8f5e9
style Twilio fill:#fff4e6
getAvailableCountries()
Get list of all countries that have regulatory requirements.
Signature:
const getAvailableCountries = async();
Returns:
Promise<Array<string>>; // Array of ISO country codes (e.g., ['GB', 'US', 'DE', 'AU'])
Business Logic:
-
Check Cache
- Uses cache key:
'available_countries' - 24-hour TTL
- Uses cache key:
-
Fetch All Regulations
- Calls
client.numbers.v2.regulatoryCompliance.regulations.list() - No filters - gets all regulations
- Calls
-
Extract Unique Countries
- Maps through regulations
- Extracts
isoCountryfield - Returns unique country codes
-
Cache and Return
Example Usage:
// Get all countries with regulatory requirements
const countries = await complianceProvider.getAvailableCountries();
console.log('Countries with requirements:', countries);
// Output: ['GB', 'US', 'DE', 'AU', 'AT', 'BE', 'CH', 'CZ', 'DK', ...]
// Check if specific country has requirements
const hasRequirements = countries.includes('GB');
console.log('GB has requirements:', hasRequirements); // true
// Display to users
countries.forEach(country => {
console.log(`${country}: Regulatory compliance required`);
});
💡 Integration Examples
Pre-Purchase Compliance Check
async function checkComplianceBeforePurchase(countryCode, numberType) {
// 1. Check if country has requirements
const requirements = await complianceProvider.getRegulationRequirements(
countryCode,
numberType,
'business',
);
if (!requirements.hasRequirements) {
return {
canPurchase: true,
requiresCompliance: false,
message: 'No regulatory requirements',
};
}
// 2. Check what's needed
const needed = {
address: requirements.requirements.address.length > 0,
endUser: requirements.requirements.end_user.length > 0,
documents: requirements.requirements.supporting_document.length > 0,
};
// 3. Check if we have everything
const account = await Account.findOne({
/* ... */
});
const hasAddress = !!account?.twilio_account?.a2p?.customer_profile_address;
const hasEndUser = account?.twilio_account?.a2p?.end_users?.length > 0;
const hasBundle = !!account?.twilio_account?.a2p?.customer_profile_bundle;
const ready =
(!needed.address || hasAddress) &&
(!needed.endUser || hasEndUser) &&
(!needed.documents || hasBundle);
return {
canPurchase: ready,
requiresCompliance: true,
needed,
hasCompleted: {
address: hasAddress,
endUser: hasEndUser,
bundle: hasBundle,
},
requirements,
};
}
Display Compliance Requirements UI
async function getComplianceRequirementsForUI(countryCode) {
const requirements = await complianceProvider.getRegulationRequirements(
countryCode,
'local',
'business',
);
if (!requirements.hasRequirements) {
return {
required: false,
message: 'No documentation needed for this country',
};
}
const ui = {
required: true,
sections: [],
};
// Address section
if (requirements.requirements.address.length > 0) {
const addressReq = requirements.requirements.address[0];
ui.sections.push({
type: 'address',
title: 'Business Address',
description: 'Provide your business address for verification',
fields: addressReq.detailed_fields.map(f => ({
name: f.machine_name,
label: f.friendly_name,
required: true,
placeholder: f.description || `Enter ${f.friendly_name.toLowerCase()}`,
})),
});
}
// End user section
if (requirements.requirements.end_user.length > 0) {
requirements.requirements.end_user.forEach(endUserReq => {
ui.sections.push({
type: 'end_user',
title: endUserReq.name,
description: 'Provide business or individual information',
fields: endUserReq.fields || [],
});
});
}
// Documents section
if (requirements.requirements.supporting_document.length > 0) {
requirements.requirements.supporting_document.forEach(docReq => {
ui.sections.push({
type: 'document',
title: docReq.name,
description: 'Upload required documentation',
acceptedTypes: docReq.accepted_documents || [],
});
});
}
return ui;
}
Country Availability Dashboard
async function generateComplianceDashboard() {
const countriesWithRequirements = await complianceProvider.getAvailableCountries();
const dashboard = {
totalCountries: countriesWithRequirements.length,
byNumberType: {
local: [],
mobile: [],
tollFree: [],
},
byComplexity: {
simple: [], // Only address
moderate: [], // Address + end user
complex: [], // All requirements
},
};
// Analyze each country
for (const country of countriesWithRequirements) {
// Check each number type
for (const numberType of ['local', 'mobile', 'toll_free']) {
const req = await complianceProvider.getRegulationRequirements(
country,
numberType,
'business',
);
if (req.hasRequirements) {
dashboard.byNumberType[numberType].push(country);
// Categorize by complexity
const reqCount =
(req.requirements.address?.length || 0) +
(req.requirements.end_user?.length || 0) +
(req.requirements.supporting_document?.length || 0);
if (reqCount === 1) {
dashboard.byComplexity.simple.push(`${country} (${numberType})`);
} else if (reqCount === 2) {
dashboard.byComplexity.moderate.push(`${country} (${numberType})`);
} else {
dashboard.byComplexity.complex.push(`${country} (${numberType})`);
}
}
}
}
return dashboard;
}
Compliance Validation
async function validateCompliance(accountId, countryCode, numberType) {
// 1. Get requirements
const requirements = await complianceProvider.getRegulationRequirements(
countryCode,
numberType,
'business',
);
if (!requirements.hasRequirements) {
return { valid: true, message: 'No requirements' };
}
// 2. Get account compliance data
const account = await Account.findById(accountId);
const a2p = account?.twilio_account?.a2p;
const validation = {
valid: true,
errors: [],
warnings: [],
};
// 3. Validate address
if (requirements.requirements.address.length > 0) {
if (!a2p?.customer_profile_address) {
validation.valid = false;
validation.errors.push('Address is required but not provided');
} else {
// Check required fields
const requiredFields = requirements.requirements.address[0].fields;
const address = a2p.customer_profile_address;
requiredFields.forEach(field => {
if (!address[field]) {
validation.valid = false;
validation.errors.push(`Address field '${field}' is required`);
}
});
}
}
// 4. Validate end user
if (requirements.requirements.end_user.length > 0) {
if (!a2p?.end_users || a2p.end_users.length === 0) {
validation.valid = false;
validation.errors.push('End user information is required');
}
}
// 5. Validate documents
if (requirements.requirements.supporting_document.length > 0) {
if (!a2p?.customer_profile_bundle) {
validation.valid = false;
validation.errors.push('Customer profile bundle is required');
} else if (a2p.customer_profile_bundle.status !== 'approved') {
validation.warnings.push(
`Bundle status is ${a2p.customer_profile_bundle.status}, not approved`,
);
}
}
return validation;
}
🚨 Error Handling
Common Errors
1. Invalid Country Code
Error: 20404 - The requested resource was not found
- Cause: Country code not recognized by Twilio
- Solution: Use ISO 3166-1 alpha-2 codes (GB, US, not UK, USA)
2. Authentication Failed
Error: 20003 - Authenticate;
- Cause: Invalid credentials
- Solution: Verify accountSid and authToken
3. Rate Limit Exceeded
Error: 20429 - Too Many Requests
- Cause: Too many API calls
- Solution: Leverage 24-hour cache, reduce redundant calls
⚡ Performance Considerations
Caching Strategy
- TTL: 24 hours (regulations rarely change)
- Storage: In-memory Map (resets on service restart)
- Production Only: Caching disabled in development
- Cache Keys: Unique per country/numberType/endUserType
Optimization Tips
- Batch Country Checks: Get available countries once, cache locally
- Reuse Requirements: Don't re-fetch for same country repeatedly
- Lazy Loading: Only fetch requirements when user selects country
- Pre-fetch Common: Cache US, GB, CA requirements on startup
🔗 Related Documentation
- Twilio Integration Overview - Main Twilio integration
- A2P Provider - A2P registration (US compliance)
- Address Provider - Address management
- Phone Numbers - Number purchase with bundleSid
External Resources: