# Error Handling

All eCommunicate API errors follow a consistent JSON format with standard HTTP status codes. This guide covers the error response structure, common error scenarios, and delivery failure codes.

## Error response format

Every error response contains four fields:

```json
{
  "status": "ERROR",
  "message": "Description of what went wrong",
  "requestId": "c3a1f8e2-4b6d-11ee-be56-0242ac120002",
  "timestamp": "2026-01-15T10:30:00Z"
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `status` | string | Always `"ERROR"` for failed requests |
| `message` | string | Human-readable error description |
| `requestId` | string | Unique UUID for the request — **always include this when contacting support** |
| `timestamp` | string | ISO 8601 timestamp of when the error occurred |


requestId
The `requestId` is your most valuable debugging tool. It allows the eCommunicate support team to trace your exact request through the system. Always log it and include it in support tickets.

## HTTP status codes

| Code | Status | Description | Action |
|  --- | --- | --- | --- |
| `200` | OK | Request succeeded | Process the response |
| `202` | Accepted | Message queued for delivery | Track delivery via webhooks or reports |
| `400` | Bad Request | Malformed request or missing required fields | Validate your request payload |
| `401` | Unauthorized | Missing or invalid API key | Check the `Authorization` header |
| `403` | Forbidden | Valid key but insufficient permissions | Contact support about account permissions |
| `404` | Not Found | Endpoint or resource does not exist | Verify the URL and resource ID |
| `429` | Too Many Requests | Rate limit exceeded | Implement exponential backoff — see [Rate Limits](/guides/rate-limits) |
| `500` | Internal Server Error | Server-side error | Retry with exponential backoff; contact support if persistent |


## Common error scenarios

### 400 Bad Request

The most common error. Check these causes:

Invalid Parameters
- Missing required fields (`sender`, `messages`, `templateName` for notifications)
- Invalid `messageType` value — must be `NOTIFICATION`, `CHAT`, or `CONVERSATION`
- Template body has `{{n}}` placeholders but no `body` component provided
- Invalid phone number format when addressing by phone — international format, leading `+` optional, no leading `0` (e.g. `+27...` or `27...`)
- `channel` field not set to `WHATSAPP` or `SMS` on the unified endpoint
- `templateId` does not match an existing approved template
- Empty `recipients` array, or a recipient with **neither** `to` **nor** `customerUserId` — supply at least one


A malformed BSUID fails as a missing recipient
`customerUserId` is validated against the `^[A-Z]{2}.[0-9]+$` format. A malformed BSUID is **silently ignored**, so a recipient sent with only a bad `customerUserId` (and no `to`) fails as though it had no recipient at all — the error won't mention the BSUID. Double-check the BSUID format before sending phone-less recipients.

### 401 Unauthorized

| Cause | Fix |
|  --- | --- |
| API key missing from header | Add `Authorization: YOUR_API_KEY` to every request |
| API key was regenerated | Use the new key — old keys are permanently invalidated |
| API key is malformed | Verify the key is a valid UUID |


### 403 Forbidden

| Cause | Fix |
|  --- | --- |
| Insufficient permissions | Contact support to verify your account role and permissions |
| Wrong client context | You may be attempting to access resources belonging to a different client |


### 404 Not Found

| Cause | Fix |
|  --- | --- |
| Incorrect endpoint URL | Double-check the path against the API Reference |
| Invalid resource ID | Verify the template, webhook, conversation, or ticket ID exists |


## Message delivery failures

When a message is accepted (`202`) but fails to deliver, the failure is reported via [webhooks](/guides/webhooks) (in the `statusUpdateURL` payload) or the Message Reports API.

### Delivery failure codes

| Code | Meaning | Recommended action |
|  --- | --- | --- |
| `4` | Invalid number | Verify the phone number format and that it's a real number |
| `22` | Blacklisted number | The recipient has been blacklisted — check your blacklist settings |
| `23` | Blocked number | The recipient has blocked your number |
| `54` | Non-WhatsApp number | Recipient is not on WhatsApp — enable SMS fallback. SMS fallback needs a phone number, so it isn't available for a recipient addressed by `customerUserId` (BSUID) alone |
| `56` | Failed | Generic delivery failure — check the `failureReason` field for details |
| `58` | Duplicate message | This message was already sent — check your deduplication logic |
| `59` | Sender not connected | WhatsApp Lite sender is disconnected — reconnect via QR code |
| `60` | WhatsApp Lite server unavailable | The Lite server is temporarily down — retry later |
| `130429` | Rate limit hit | You're sending too fast — implement throttling and backoff |
| `131026` | Message undeliverable | Meta could not deliver the message — verify the recipient and template |
| `131047` | Re-engagement required | 24-hour session expired — send a template message to re-engage |
| `131053` | Media upload failed | The media URL is unreachable or the file is too large |
| `132000` | Template paused | Your template has been paused due to quality issues |
| `132001` | Template disabled | Your template has been permanently disabled |


## Troubleshooting flowchart

Use this decision tree when a message fails:

1. **Check the HTTP status code**
  - `4xx` → Fix the request (see scenarios above)
  - `5xx` → Retry with exponential backoff
  - `202` → Message was accepted; check delivery status below
2. **Check delivery status** via webhook or Message Reports API
  - `DELIVERED` / `READ` → Success
  - `FAILED` → Check `failureCode` and `failureReason`
3. **Check the failure code**
  - Codes `4`, `54` → Phone number issue
  - Codes `22`, `23` → Recipient blocked/blacklisted
  - Codes `59`, `60` → WhatsApp Lite connection issue
  - Codes `130xxx` → Meta Cloud API issue
  - Codes `132xxx` → Template issue
4. **If still stuck** → Contact [support@ecommunicate.co.za](mailto:support@ecommunicate.co.za) with the `requestId`


## Retry strategy

For transient errors (`429`, `500`, `502`, `503`), implement exponential backoff:

```javascript
async function sendWithRetry(payload, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await sendMessage(payload);
      if (response.status === 429 || response.status >= 500) {
        throw new Error(`HTTP ${response.status}`);
      }
      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
      console.log(`Retry ${attempt + 1} in ${delay}ms: ${error.message}`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

Do not retry 4xx errors
Client errors (`400`, `401`, `403`, `404`) indicate a problem with your request. Retrying will produce the same result. Fix the request first.