Skip to main content

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.

Every failure surfaced by the SDK is a 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

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
  }
}
SubclassMaps from gRPC
AuthenticationErrorUNAUTHENTICATED, PERMISSION_DENIED
NotFoundErrorNOT_FOUND
RateLimitErrorRESOURCE_EXHAUSTED
ValidationErrorINVALID_ARGUMENT, FAILED_PRECONDITION
ConnectionErrorUNAVAILABLE, DEADLINE_EXCEEDED
WhatsAppErrorEverything else

Error fields

Every WhatsAppError carries:
FieldDescription
codeCanonical error code — one of ErrorCode.
retryabletrue when the caller can safely retry with backoff.
grpcCodeNumeric gRPC status code (mirrors nice-grpc-common’s Status enum).
contextRecord<string, string> of extra server-provided context.
causeThe underlying error, when one was thrown by the transport.

Error codes

CodeValue
unauthenticated"unauthenticated"
unauthorized"unauthorized"
rateLimitExceeded"rateLimitExceeded"
notFound"notFound"
invalidArgument"invalidArgument"
preconditionFailed"preconditionFailed"
serviceUnavailable"serviceUnavailable"
timeout"timeout"
internalError"internalError"
networkError"networkError"
Switch on code when you want to react to a specific failure mode rather than a whole class:
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:
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. Errors from unary calls (messages.send, media.upload, events.fetchMissed) throw.