Error Handling
Error Handling
The SDK provides a structured error hierarchy for all A2P registration failure modes.
Error Hierarchy
All errors extend A2PRegistrationError:
class A2PRegistrationError extends Error {
step: string; // e.g., "customer_profile", "brand_registration"
sid?: string; // Resource SID (if available)
failureReason?: string; // Twilio's failure reason
guidance?: string; // Actionable troubleshooting advice
}Error Types
| Error Class | Step | When Thrown |
|---|---|---|
ProfileRejectedError | customer_profile | Customer Profile or Starter Profile rejected |
TrustProductFailedError | trust_product | Trust Product fails evaluation or review |
BrandFailedError | brand_registration | Brand fails TCR review |
BrandSuspendedError | brand_registration | Brand is suspended |
CampaignFailedError | campaign_registration | Campaign fails manual vetting |
OtpVerificationError | sole_prop_otp_verification | OTP verification times out |
Catching Errors
Use instanceof checks to handle specific errors:
import {
A2PRegistrationError,
ProfileRejectedError,
BrandFailedError,
CampaignFailedError,
} from '@warp-message/a2p-sdk';
try {
const result = await flows.directStandard.step1_createCustomerProfile(client, input);
} catch (error) {
if (error instanceof ProfileRejectedError) {
console.error('Profile rejected:', error.guidance);
// Error has: step, sid, failureReason, guidance
} else if (error instanceof BrandFailedError) {
console.error('Brand failed:', error.guidance);
} else if (error instanceof A2PRegistrationError) {
console.error('Registration error:', error.step, error.guidance);
} else {
console.error('Unexpected error:', error);
}
}Error Guidance
Each error includes a guidance property with actionable troubleshooting advice:
ProfileRejectedError
Guidance: "The email domain must not be disposable. The address must be a valid US or Canadian address. Check the Twilio Console for detailed rejection reasons."
Common causes:
- Disposable email domain (e.g., temp-mail.org)
- Invalid US/Canadian address
- Business name doesn't match public records
BrandFailedError
Guidance: "Common causes: business name mismatch with tax records, invalid EIN, or incomplete business information. See the Twilio Console for TCR rejection details."
Common causes:
- Business name doesn't match IRS/tax records exactly
- Invalid or mismatched Tax ID (EIN/CBN)
- Incomplete business information
BrandSuspendedError
Guidance: "The Brand potentially violates one or more rules. Contact Twilio Support for details."
Action: No programmatic fix. Contact Twilio Support.
CampaignFailedError
Guidance: "You can edit and resubmit the campaign (additional $15 fee may apply). Common issues: vague description, insufficient message samples, or policy violations."
Common causes:
- Vague campaign description (< 40 chars or too generic)
- Insufficient message samples (< 2 samples)
- Policy violations (spam, adult content, etc.)
Action: Edit campaign and resubmit (costs $15).
OtpVerificationError
Guidance: "The customer must respond to the OTP SMS within 24 hours. You can re-trigger the OTP from the Twilio Console. The mobile number can only be used 3 times total for Sole Proprietor registrations across all vendors."
Common causes:
- Customer didn't verify OTP within 24 hours
- OTP SMS blocked or not delivered
- Mobile number already used 3 times (hard limit across ALL vendors)
Error Chaining
All errors use the standard cause property for error chaining:
try {
const result = await step1_createCustomerProfile(client, input);
} catch (error) {
if (error instanceof A2PRegistrationError) {
console.error('Original error:', error.cause);
}
}Catch-All Handler
Build a generic error handler:
async function handleRegistrationStep<T>(
fn: () => Promise<T>
): Promise<T | null> {
try {
return await fn();
} catch (error) {
if (error instanceof A2PRegistrationError) {
console.error(`[${error.step}] Registration failed:`, error.guidance);
if (error.failureReason) {
console.error('Twilio reason:', error.failureReason);
}
return null;
}
throw error; // Re-throw unexpected errors
}
}
// Usage
const result = await handleRegistrationStep(() =>
flows.directStandard.step1_createCustomerProfile(client, input)
);