Skip to main content

Twilio A2P Provider

🎯 Overview

The A2P (Application-to-Person) Provider (Providers/a2p.js) manages A2P 10DLC brand and campaign registration required for high-volume SMS messaging to US phone numbers.

Source: external/Integrations/Twilio/Providers/a2p.js

What is A2P 10DLC?

A2P 10DLC is a system in the United States for long code (10-digit) phone numbers that send Application-to-Person (A2P) messages. Carriers require businesses to register their brand and campaigns to reduce spam and improve deliverability.

Key Capabilities:

  • Fetch customer profile policies
  • Create customer profile bundles (TrustHub)
  • Manage end-user objects (business/individual info)
  • Create and manage address objects
  • Save registration submission data
  • Support for Starter and Secondary customer profiles

Why A2P Registration?

  • Required: US carriers require A2P registration for business SMS
  • Deliverability: Registered brands have better message delivery rates
  • Compliance: Avoid message blocking and account suspension
  • Throughput: Higher message-per-second limits with registration

🔌 Provider Functions

getStarterCustomerProfilePolicy()

Fetch the Starter Customer Profile Policy from TrustHub.

Signature:

const getStarterCustomerProfilePolicy = async(metadata, id);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token} and auth
idStringPolicy SID (default: RN806dd6cd175f314e1f96a9727ee271f4)

Constants:

  • STARTER_CUSTOMER_PROFILE_POLICY_ID: 'RN806dd6cd175f314e1f96a9727ee271f4'
  • SECONDARY_CUSTOMER_PROFILE_POLICY_ID: 'RNdfbf3fae0e1107f8aded0e7cead80bf5'

Returns:

Promise<{
sid: string;
friendlyName: string;
requirements: Object;
}>;

Example Usage:

const a2pProvider = require('./Providers/a2p');

// Get Starter profile policy
const policy = await a2pProvider.getStarterCustomerProfilePolicy(
{
twilio_creds: {
sid: accountSID,
token: authToken,
},
},
'RN806dd6cd175f314e1f96a9727ee271f4',
);

console.log('Policy:', policy.friendlyName);
console.log('Requirements:', policy.requirements);

saveRegistrationSubmission()

Save A2P registration submission data to DashClicks account.

Signature:

const saveRegistrationSubmission = async(metadata, data);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token} and type
dataObjectRegistration submission payload

Business Logic:

  • Updates DashClicks account with:
    • twilio_account.a2p.paid: true
    • twilio_account.a2p.type: metadata.type
    • twilio_account.a2p.submission_data: data

Example Usage:

await a2pProvider.saveRegistrationSubmission(
{
twilio_creds: { sid: accountSID, token: authToken },
type: 'starter', // or 'secondary'
},
{
businessName: 'DashClicks LLC',
ein: '12-3456789',
website: 'https://dashclicks.com',
submittedAt: new Date(),
},
);

createCustomerProfile()

Create a Customer Profile Bundle in TrustHub.

Signature:

const createCustomerProfile = async(metadata, data, test);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
dataObjectBundle data: {friendlyName, email, policySid, statusCallback}
testBooleanIf true, skips database update (for testing)

data Object:

FieldTypeRequiredDescription
friendlyNameStringDisplay name for bundle
emailStringContact email
policySidStringPolicy SID (Starter or Secondary)
statusCallbackStringWebhook URL for status updates

Returns:

Promise<{
sid: string; // Bundle SID (BU...)
accountSid: string;
policySid: string;
friendlyName: string;
email: string;
status: string; // "draft", "pending-review", "in-review", "twilio-approved"
validUntil: Date;
dateCreated: Date;
dateUpdated: Date;
}>;

Business Logic:

  1. Create Customer Profile Bundle
    • Uses TrustHub API
  2. Store in Database (unless test mode)
    • Saves to twilio_account.a2p.customer_profile_bundle

Example Usage:

// Create customer profile bundle
const bundle = await a2pProvider.createCustomerProfile(
{
twilio_creds: { sid: accountSID, token: authToken },
},
{
friendlyName: 'DashClicks Business Profile',
email: 'support@dashclicks.com',
policySid: 'RN806dd6cd175f314e1f96a9727ee271f4', // Starter policy
statusCallback: 'https://api.dashclicks.com/webhooks/a2p-status',
},
false, // Production mode
);

console.log('Bundle SID:', bundle.sid);
console.log('Status:', bundle.status); // draft

createEndUserObject()

Create an end-user object (business or individual information).

Signature:

const createEndUserObject = async(metadata, data, test);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
dataObjectEnd user data: {attributes, friendlyName, type}
testBooleanIf true, skips database update

data Object:

FieldTypeRequiredDescription
friendlyNameStringDisplay name
typeStringEnd user type: 'customer_profile_business_information' or 'authorized_representative_1'
attributesObjectBusiness/individual details

attributes Object (Business):

{
business_name: 'DashClicks LLC',
business_type: 'llc', // llc, corporation, partnership, etc.
business_registration_number: '12345',
business_identity: 'direct_customer', // direct_customer or isvreseller
social_media_profile_urls: ['https://twitter.com/dashclicks'],
website_url: 'https://dashclicks.com',
business_regions_of_operation: 'US',
phone_number: '+12125551234',
email: 'business@dashclicks.com',
business_registration_identifier: 'ein', // ein or tax_id
business_identity_number: '12-3456789'
}

Returns:

Promise<{
sid: string; // End user SID (IT...)
accountSid: string;
friendlyName: string;
type: string;
attributes: Object;
dateCreated: Date;
dateUpdated: Date;
}>;

Business Logic:

  1. Create End User via TrustHub
  2. Push to Database Array (unless test mode)
    • Adds to twilio_account.a2p.end_users array

Example Usage:

// Create business end-user
const endUser = await a2pProvider.createEndUserObject(
{
twilio_creds: { sid: accountSID, token: authToken },
},
{
friendlyName: 'DashClicks Business Info',
type: 'customer_profile_business_information',
attributes: {
business_name: 'DashClicks LLC',
business_type: 'llc',
business_registration_number: '12345',
business_identity: 'direct_customer',
website_url: 'https://dashclicks.com',
business_regions_of_operation: 'US',
phone_number: '+12125551234',
email: 'support@dashclicks.com',
business_registration_identifier: 'ein',
business_identity_number: '12-3456789',
},
},
false,
);

console.log('End User SID:', endUser.sid);

updateEndUserObject()

Update an existing end-user object.

Signature:

const updateEndUserObject = async(metadata, id, data, test);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
idStringEnd user SID (IT...)
dataObjectUpdated data
testBooleanIf true, skips database update

Example Usage:

// Update business information
const updated = await a2pProvider.updateEndUserObject(
metadata,
'IT1234567890abcdef',
{
attributes: {
website_url: 'https://new-domain.com',
phone_number: '+12125559876',
},
},
false,
);

deleteEndUserObject()

Delete an end-user object.

Signature:

const deleteEndUserObject = async(metadata, id);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
idStringEnd user SID to delete

Example Usage:

await a2pProvider.deleteEndUserObject(metadata, 'IT1234567890abcdef');

createAddressObject()

Create an address object for customer profile.

Signature:

const createAddressObject = async(metadata, data, test);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
dataObjectAddress data
testBooleanIf true, skips database update

data Object:

{
customerName: 'DashClicks LLC',
street: '123 Main Street',
streetSecondary: 'Suite 100', // Optional
city: 'New York',
region: 'NY',
postalCode: '10001',
isoCountry: 'US'
}

Returns:

Promise<{
sid: string; // Address SID (AD...)
accountSid: string;
customerName: string;
street: string;
city: string;
region: string;
postalCode: string;
isoCountry: string;
dateCreated: Date;
dateUpdated: Date;
}>;

Business Logic:

  1. Create Address via Twilio Addresses API
  2. Store in Database (unless test mode)
    • Saves to twilio_account.a2p.customer_profile_address

Example Usage:

// Create business address
const address = await a2pProvider.createAddressObject(
{
twilio_creds: { sid: accountSID, token: authToken },
},
{
customerName: 'DashClicks LLC',
street: '123 Main Street',
streetSecondary: 'Suite 100',
city: 'New York',
region: 'NY',
postalCode: '10001',
isoCountry: 'US',
},
false,
);

console.log('Address SID:', address.sid);

updateAddressObject()

Update an existing address object.

Signature:

const updateAddressObject = async(metadata, id, data, test);

Parameters:

ParameterTypeRequiredDescription
metadataObjectContains twilio_creds: {sid, token}
idStringAddress SID (AD...)
dataObjectUpdated address data
testBooleanIf true, skips database update

Example Usage:

// Update address
const updated = await a2pProvider.updateAddressObject(
metadata,
'AD1234567890abcdef',
{
street: '456 New Street',
postalCode: '10002',
},
false,
);

💡 Integration Examples

Complete A2P Registration Flow

async function registerA2PBrand(accountId) {
const account = await Account.findById(accountId);
const metadata = {
twilio_creds: {
sid: account.twilio_account.sid,
token: account.twilio_account.authToken,
},
type: 'starter',
};

// Step 1: Create address
const address = await a2pProvider.createAddressObject(
metadata,
{
customerName: account.businessName,
street: account.address.street,
city: account.address.city,
region: account.address.state,
postalCode: account.address.zip,
isoCountry: 'US',
},
false,
);

// Step 2: Create end user (business info)
const endUser = await a2pProvider.createEndUserObject(
metadata,
{
friendlyName: `${account.businessName} - Business Info`,
type: 'customer_profile_business_information',
attributes: {
business_name: account.businessName,
business_type: account.businessType || 'llc',
business_registration_number: account.registrationNumber,
business_identity: 'direct_customer',
website_url: account.website,
business_regions_of_operation: 'US',
phone_number: account.phone,
email: account.email,
business_registration_identifier: 'ein',
business_identity_number: account.ein,
},
},
false,
);

// Step 3: Create customer profile bundle
const bundle = await a2pProvider.createCustomerProfile(
metadata,
{
friendlyName: `${account.businessName} A2P Bundle`,
email: account.email,
policySid: 'RN806dd6cd175f314e1f96a9727ee271f4', // Starter policy
statusCallback: `${process.env.API_URL}/webhooks/a2p-status/${accountId}`,
},
false,
);

// Step 4: Save registration data
await a2pProvider.saveRegistrationSubmission(metadata, {
addressSid: address.sid,
endUserSid: endUser.sid,
bundleSid: bundle.sid,
registeredAt: new Date(),
status: bundle.status,
});

return {
address,
endUser,
bundle,
};
}

Check A2P Registration Status

async function checkA2PStatus(accountId) {
const account = await Account.findById(accountId);
const a2p = account?.twilio_account?.a2p;

if (!a2p?.customer_profile_bundle) {
return {
registered: false,
status: 'not_started',
message: 'A2P registration not started',
};
}

return {
registered: a2p.paid || false,
status: a2p.customer_profile_bundle.status,
bundleSid: a2p.customer_profile_bundle.sid,
type: a2p.type,
hasAddress: !!a2p.customer_profile_address,
hasEndUsers: a2p.end_users?.length > 0,
submittedData: a2p.submission_data,
};
}

A2P Status Webhook Handler

// Handle status callback from Twilio
router.post('/webhooks/a2p-status/:accountId', async (req, res) => {
const { accountId } = req.params;
const { BundleSid, Status } = req.body;

console.log(`A2P Bundle ${BundleSid} status: ${Status}`);

// Update bundle status in database
await Account.updateOne(
{
_id: accountId,
'twilio_account.a2p.customer_profile_bundle.sid': BundleSid,
},
{
$set: {
'twilio_account.a2p.customer_profile_bundle.status': Status,
},
},
);

// Handle status changes
if (Status === 'twilio-approved') {
// A2P approved - can now send high-volume SMS
await notifyA2PApproved(accountId);
} else if (Status === 'twilio-rejected') {
// A2P rejected - notify user
await notifyA2PRejected(accountId, req.body.RejectionReason);
}

res.sendStatus(200);
});

🚨 Error Handling

Common Errors

1. Missing Required Fields

Error: Required field 'business_name' is missing
  • Cause: Incomplete end user attributes
  • Solution: Provide all required business information

2. Invalid EIN Format

Error: Invalid business_identity_number format
  • Cause: EIN not in XX-XXXXXXX format
  • Solution: Format EIN as 12-3456789

3. Bundle Already Exists

Error: A bundle already exists for this policy
  • Cause: Attempting to create duplicate bundle
  • Solution: Update existing bundle or delete old one

External Resources:

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM