> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildwithchirp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Errors

> Learn how to handle API errors and webhook failures in your application

This guide shows you how to handle errors from the Chirp API in your application. For a full reference of the error format, see [Errors](/api_reference/errors). For details on specific error codes, see [Error Codes](/api_reference/error-codes).

## API Error Handling

### Basic Error Handling

Every error response from the Chirp API has the same structure. Start by checking the `error.type` field to determine the category of the problem.

<CodeGroup>
  ```javascript icon="javascript" title="Basic error handling" theme={null}
  const response = await fetch("https://api.buildwithchirp.com/v1/sms", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: "+15551234567",
      to: ["+15559876543"],
      text: "Hello from Chirp!",
    }),
  });

  if (!response.ok) {
    const { error } = await response.json();

    switch (error.type) {
      case "invalid_request_error":
        // Fix the request and try again
        console.error(`Bad request: ${error.message}`);
        break;
      case "authentication_error":
        // Check your API key
        console.error(`Auth failed: ${error.message}`);
        break;
      case "provider_error":
        // The request was valid but the provider rejected it
        console.error(`Provider error: ${error.message}`);
        break;
      case "api_error":
        // Something went wrong on Chirp's end - retry later
        console.error(`Server error: ${error.message}`);
        break;
    }
  }
  ```

  ```bash icon="terminal" title="cURL error handling" theme={null}
  response=$(curl -s -w "\n%{http_code}" -X POST https://api.buildwithchirp.com/v1/sms \
    -H "Authorization: Bearer $CHIRP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "from": "+15551234567",
      "to": ["+15559876543"],
      "text": "Hello from Chirp!"
    }')

  http_code=$(echo "$response" | tail -1)
  body=$(echo "$response" | head -1)

  if [ "$http_code" -ge 400 ]; then
    echo "Error ($http_code): $(echo $body | jq -r '.error.message')"
  fi
  ```
</CodeGroup>

### Handling Specific Error Codes

When an error includes a `code` field, you can use it to branch your logic and take specific actions.

```javascript icon="javascript" title="Handling specific error codes" theme={null}
async function sendWhatsAppMessage(to, text) {
  const response = await fetch("https://api.buildwithchirp.com/v1/whatsapp/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ to, type: "text", text: { body: text } }),
  });

  if (!response.ok) {
    const { error } = await response.json();

    switch (error.code) {
      case "conversation_window_expired":
        // Fall back to sending a template message
        return sendTemplateMessage(to);

      case "recipient_not_on_whatsapp":
        // Fall back to SMS
        return sendSms(to, text);

      case "message_undeliverable":
        // Mark recipient as unreachable
        await markUndeliverable(to, error.provider?.code);
        break;

      case "rate_limit_exceeded":
      case "whatsapp_rate_limited":
        // Retry with backoff
        return retryWithBackoff(() => sendWhatsAppMessage(to, text));

      default:
        throw new Error(`Unexpected error: ${error.message}`);
    }
  }

  return response.json();
}
```

### Handling Provider Errors

When an error has a `provider` object, you can access the raw error from Meta or Telnyx for additional debugging context.

```javascript icon="javascript" title="Handling provider errors" theme={null}
if (!response.ok) {
  const { error } = await response.json();

  if (error.provider) {
    console.error("Provider error details:", {
      source: error.provider.source,
      code: error.provider.code,
      message: error.provider.message,
    });

    // If it's a Meta error, save the trace ID for support
    if (error.provider.fbtrace_id) {
      await saveTraceId(messageId, error.provider.fbtrace_id);
    }
  }
}
```

### Handling Validation Errors

When the `param` field is present, it tells you which request parameter caused the error. Use this to provide targeted feedback in your UI.

```javascript icon="javascript" title="Handling validation errors" theme={null}
if (!response.ok) {
  const { error } = await response.json();

  if (error.type === "invalid_request_error" && error.param) {
    // Highlight the specific form field that has an error
    setFieldError(error.param, error.message);
  }
}
```

## Webhook Error Handling

When a message fails to deliver, Chirp sends a webhook event with the error details in the same format as API errors.

### Receiving Failed Events

Failed delivery events include an `error` object in the `data` payload.

```javascript icon="javascript" title="Webhook error handling" theme={null}
app.post("/webhooks/chirp", (req, res) => {
  const { event, data } = req.body;

  if (event === "messages.whatsapp.failed") {
    const { error, message } = data;

    console.error(`Message ${message.id} failed:`, {
      type: error.type,
      code: error.code,
      message: error.message,
      provider: error.provider,
    });

    // Handle specific failure reasons
    switch (error.code) {
      case "conversation_window_expired":
        // Queue a template message instead
        queueTemplateMessage(message.to);
        break;

      case "account_restricted":
        // Alert operations team
        alertOpsTeam("WhatsApp account restricted", error);
        break;

      case "message_undeliverable":
        // Update delivery status in your database
        markMessageFailed(message.id, error.code);
        break;
    }
  }

  if (event === "messages.sms.failed") {
    const { error, message } = data;

    console.error(`SMS ${message.id} failed:`, {
      code: error.code,
      message: error.message,
      provider: error.provider,
    });
  }

  res.sendStatus(200);
});
```

### Webhook Failure Payload

Here is an example of a failed WhatsApp message webhook:

```json icon="code" title="WhatsApp failure webhook" theme={null}
{
  "event": "messages.whatsapp.failed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "message": {
      "id": "msg_abc123",
      "from": "+15551234567",
      "to": "+15559876543",
      "type": "text"
    },
    "app": {
      "id": "app_xyz"
    },
    "error": {
      "type": "provider_error",
      "code": "message_undeliverable",
      "message": "Unable to deliver message. The recipient may have blocked your number.",
      "provider": {
        "source": "meta",
        "code": 131026,
        "message": "Unable to deliver message"
      }
    }
  }
}
```

## Retry Strategies

Not all errors should be retried. Use the following table to determine whether an error is retryable.

### When to Retry

| Error Type / Code             | Retryable | Strategy                                   |
| ----------------------------- | --------- | ------------------------------------------ |
| `api_error` (any)             | Yes       | Exponential backoff                        |
| `meta_api_unavailable`        | Yes       | Exponential backoff, max 5 retries         |
| `telnyx_api_unavailable`      | Yes       | Exponential backoff, max 5 retries         |
| `meta_api_error`              | Maybe     | Retry once; if it fails again, investigate |
| `telnyx_api_error`            | Maybe     | Retry once; if it fails again, investigate |
| `rate_limit_exceeded`         | Yes       | Wait for `Retry-After` header, then retry  |
| `whatsapp_rate_limited`       | Yes       | Wait and retry with backoff                |
| `invalid_request_error` (any) | No        | Fix the request parameters                 |
| `authentication_error` (any)  | No        | Fix the API key                            |
| `message_undeliverable`       | No        | Permanent failure; do not retry            |
| `conversation_window_expired` | No        | Send a template message instead            |
| `account_restricted`          | No        | Resolve the restriction with Meta          |

<Warning>
  Never retry `invalid_request_error` or `authentication_error` automatically. These indicate a problem with your request that will not resolve on its own. Retrying will waste your rate limit budget.
</Warning>

### Exponential Backoff

For retryable errors, use exponential backoff to avoid overwhelming the API.

```javascript icon="javascript" title="Exponential backoff" theme={null}
async function retryWithBackoff(fn, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const { error } = err;

      // Don't retry non-retryable errors
      if (
        error?.type === "invalid_request_error" ||
        error?.type === "authentication_error"
      ) {
        throw err;
      }

      // Last attempt - give up
      if (attempt === maxRetries - 1) {
        throw err;
      }

      // Calculate delay: 1s, 2s, 4s, 8s, 16s (with jitter)
      const baseDelay = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;
      const delay = baseDelay + jitter;

      console.log(`Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}
```

<Tip>
  Add random jitter to your retry delays to prevent multiple clients from retrying at the same time (thundering herd problem).
</Tip>

## Logging Best Practices

Log error details to help your team debug issues in production.

```javascript icon="javascript" title="Error logging" theme={null}
function logApiError(context, error) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    context,
    error: {
      type: error.type,
      code: error.code,
      message: error.message,
      param: error.param,
    },
  };

  // Include provider details for upstream errors
  if (error.provider) {
    logEntry.provider = {
      source: error.provider.source,
      code: error.provider.code,
      message: error.provider.message,
    };

    // fbtrace_id is critical for Meta support tickets
    if (error.provider.fbtrace_id) {
      logEntry.provider.fbtrace_id = error.provider.fbtrace_id;
    }
  }

  console.error(JSON.stringify(logEntry));
}
```

<Info>
  When contacting Meta support about a WhatsApp issue, always include the `fbtrace_id` from the error response. This allows Meta to look up the exact request in their systems.
</Info>
