API WhatsApp
Errors & Limits
Complete reference of API error codes, HTTP status codes, rate limits, and troubleshooting guide for the WhatsApp Business API.
HTTP Status Codes
| Status | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created (templates, broadcasts) |
400 Bad Request | Invalid request body or parameters |
401 Unauthorized | Missing or invalid API key |
403 Forbidden | Insufficient permissions |
404 Not Found | Resource doesn’t exist |
409 Conflict | Duplicate resource (e.g., template name) |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Server error — retry with backoff |
Error Response Format
All errors return a consistent JSON structure:
{
"error": {
"code": "INVALID_RECIPIENT",
"message": "The recipient phone number is not registered on WhatsApp.",
"details": {
"field": "to",
"value": "1234567890"
},
"request_id": "req_abc123"
}
}
Error Codes Reference
Message Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
INVALID_RECIPIENT | 400 | Number not on WhatsApp | Verify the number is registered |
INVALID_MESSAGE_TYPE | 400 | Unsupported message type | Check supported types in docs |
MESSAGE_TOO_LONG | 400 | Text exceeds 4,096 chars | Shorten the message |
OUTSIDE_WINDOW | 400 | 24h session expired | Use a template message |
TEMPLATE_NOT_FOUND | 404 | Template doesn’t exist | Check name and language code |
TEMPLATE_PAUSED | 400 | Template is paused by Meta | Fix quality issues, resubmit |
MEDIA_TOO_LARGE | 400 | File exceeds size limit | Compress or resize the file |
MEDIA_FORMAT_UNSUPPORTED | 400 | Unsupported file format | Convert to a supported format |
RECIPIENT_BLOCKED | 400 | User blocked your number | Remove from contact list |
Authentication Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
INVALID_API_KEY | 401 | API key is invalid or revoked | Check/regenerate your API key |
EXPIRED_API_KEY | 401 | API key has expired | Generate a new key |
INSUFFICIENT_PERMISSIONS | 403 | Key lacks required scope | Use a key with correct permissions |
IP_NOT_ALLOWED | 403 | Request from blocked IP | Add IP to allowlist |
Rate Limit Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
RATE_LIMITED | 429 | Too many requests/second | Wait and retry with backoff |
DAILY_LIMIT_REACHED | 429 | Daily message quota exceeded | Upgrade plan or wait until reset |
BROADCAST_LIMIT | 429 | Max broadcasts/day reached | Wait or upgrade plan |
Account Errors
| Code | HTTP | Description | Solution |
|---|---|---|---|
ACCOUNT_SUSPENDED | 403 | Account is suspended | Contact support |
WABA_DISCONNECTED | 403 | WhatsApp Business Account disconnected | Reconnect in dashboard |
NUMBER_NOT_VERIFIED | 403 | Phone number not verified | Complete verification flow |
Rate Limits
API Rate Limits (per API key)
| Plan | Requests/sec | Daily Messages |
|---|---|---|
| Free | 10 | 1,000 |
| Premium | 100 | 100,000 |
| Enterprise | Up to 1,000 | Unlimited |
WhatsApp Throughput (set by Meta)
Message throughput is controlled by Meta based on your phone number’s quality rating:
| Tier | Unique Users/24h | How to Reach |
|---|---|---|
| Tier 1 | 1,000 | New numbers start here |
| Tier 2 | 10,000 | Maintain quality + volume |
| Tier 3 | 100,000 | Consistent quality + higher volume |
| Tier 4 | Unlimited | Highest quality + highest volume |
StartMessaging handles all Meta-level throttling automatically — you don’t need to manage it yourself.
Implementing Retry Logic
async function sendWithRetry(payload, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.startmessaging.com/v1/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (response.status >= 500) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
continue;
}
return await response.json();
} catch (error) {
if (attempt === maxRetries) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
FAQ
What should I do if I keep getting OUTSIDE_WINDOW errors?
The 24-hour customer service window has expired. You must use an approved template message to re-initiate the conversation.
How do I check my current rate limit usage?
Check the X-RateLimit-Remaining and X-RateLimit-Reset response headers on any API call.
My quality rating dropped — what can I do? Reduce marketing messages, improve opt-in practices, and make it easy for users to opt out. Quality ratings recover automatically when complaint rates decrease.