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

# Error Handling

> Catch typed errors and classify with error.code

export const TypeTooltip = ({name, type, children}) => {
  const [visible, setVisible] = React.useState(false);
  const [pos, setPos] = React.useState({
    top: 0,
    left: 0
  });
  const triggerRef = React.useRef(null);
  const show = () => {
    if (triggerRef.current) {
      const rect = triggerRef.current.getBoundingClientRect();
      setPos({
        top: rect.bottom + 6,
        left: rect.left
      });
    }
    setVisible(true);
  };
  const hide = () => setVisible(false);
  return <>
      <span ref={triggerRef} onMouseEnter={show} onMouseLeave={hide} style={{
    cursor: "pointer",
    position: "relative",
    display: "inline"
  }}>
        {children || <code>{name}</code>}
      </span>
      {visible && <span style={{
    position: "fixed",
    top: pos.top,
    left: pos.left,
    zIndex: 9999,
    padding: "8px 12px",
    borderRadius: "8px",
    fontSize: "13px",
    lineHeight: "1.5",
    fontFamily: "'Azeret Mono', monospace",
    whiteSpace: "pre",
    backgroundColor: "var(--tw-prose-pre-bg, #1e1e1e)",
    color: "var(--tw-prose-pre-code, #e5e5e5)",
    border: "1px solid var(--border, rgba(128,128,128,0.2))",
    boxShadow: "0 4px 16px rgba(0,0,0,0.3)",
    pointerEvents: "none"
  }}>
          {type}
        </span>}
    </>;
};

Every failure surfaced by the SDK is a <TypeTooltip name="WhatsAppError" type={`declare class WhatsAppError extends Error {
readonly code: ErrorCode;
readonly retryable: boolean;
readonly grpcCode: number;
readonly context: Record<string, string>;
constructor(message: string, options: WhatsAppErrorOptions);
}`} /> or one of its subclasses. Branch on the subclass with `instanceof` for broad handling, or switch on `error.code` for precise recovery.

## The error hierarchy

```ts theme={null}
import {
  WhatsAppError,
  AuthenticationError,
  ConnectionError,
  NotFoundError,
  RateLimitError,
  ValidationError,
} from "@photon-ai/whatsapp-business";

try {
  await client.messages.send({ to: "+15551234567", text: "Hi" });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // bad access token, missing scopes
  } else if (error instanceof RateLimitError) {
    // backoff and retry
  } else if (error instanceof ValidationError) {
    // recipient format, content shape, template parameters
  } else if (error instanceof ConnectionError) {
    // transient network / server issue
  } else if (error instanceof NotFoundError) {
    // mediaId expired, messageId unknown
  } else if (error instanceof WhatsAppError) {
    // anything else surfaced by the SDK
  }
}
```

| Subclass              | Maps from gRPC                            |
| --------------------- | ----------------------------------------- |
| `AuthenticationError` | `UNAUTHENTICATED`, `PERMISSION_DENIED`    |
| `NotFoundError`       | `NOT_FOUND`                               |
| `RateLimitError`      | `RESOURCE_EXHAUSTED`                      |
| `ValidationError`     | `INVALID_ARGUMENT`, `FAILED_PRECONDITION` |
| `ConnectionError`     | `UNAVAILABLE`, `DEADLINE_EXCEEDED`        |
| `WhatsAppError`       | Everything else                           |

## Error fields

Every `WhatsAppError` carries:

| Field       | Description                                                            |
| ----------- | ---------------------------------------------------------------------- |
| `code`      | Canonical error code — one of [`ErrorCode`](#error-codes).             |
| `retryable` | `true` when the caller can safely retry with backoff.                  |
| `grpcCode`  | Numeric gRPC status code (mirrors `nice-grpc-common`'s `Status` enum). |
| `context`   | `Record<string, string>` of extra server-provided context.             |
| `cause`     | The underlying error, when one was thrown by the transport.            |

## Error codes

<Accordion title="ErrorCode" description="Canonical error codes returned by the server.">
  | Code                 | Value                  |
  | -------------------- | ---------------------- |
  | `unauthenticated`    | `"unauthenticated"`    |
  | `unauthorized`       | `"unauthorized"`       |
  | `rateLimitExceeded`  | `"rateLimitExceeded"`  |
  | `notFound`           | `"notFound"`           |
  | `invalidArgument`    | `"invalidArgument"`    |
  | `preconditionFailed` | `"preconditionFailed"` |
  | `serviceUnavailable` | `"serviceUnavailable"` |
  | `timeout`            | `"timeout"`            |
  | `internalError`      | `"internalError"`      |
  | `networkError`       | `"networkError"`       |
</Accordion>

Switch on `code` when you want to react to a specific failure mode rather than a whole class:

```ts theme={null}
try {
  await client.messages.send({ to, text: "Hi" });
} catch (error) {
  if (error instanceof WhatsAppError) {
    switch (error.code) {
      case "rateLimitExceeded":
        await sleep(exponentialBackoff());
        break;
      case "preconditionFailed":
        // 24-hour window expired — send a template instead
        break;
      case "unauthenticated":
        // rotate token
        break;
    }
  }
}
```

## Retrying

The SDK can retry transient errors automatically. Opt in via `ClientOptions.retry`:

```ts theme={null}
const client = createClient({
  accessToken: "...",
  phoneNumberId: "...",
  appSecret: "...",
  retry: true, // default settings
});

// Or tune the behaviour
const client2 = createClient({
  accessToken: "...",
  phoneNumberId: "...",
  appSecret: "...",
  retry: {
    maxAttempts: 5,
    initialDelay: 250,
    maxDelay: 10_000,
  },
});
```

Retries only fire when `error.retryable === true`. Non-retryable errors (validation failures, auth issues) propagate immediately — don't wrap them in your own retry loop.

## Subscribe vs unary calls

Streaming errors in `events.subscribe()` trigger the reconnect machinery rather than throwing — see [Events → Reconnection](/advanced-kits/whatsapp/events#reconnection). Errors from unary calls (`messages.send`, `media.upload`, `events.fetchMissed`) throw.
