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

# Configuração

> Configure o WaClient: sessões, timeouts, sincronização de histórico, presença ao conectar, addons, proxy, logging e botões de produção.

O `WaClient` recebe um objeto `WaClientOptions` e um logger opcional:

```ts theme={null}
const client = new WaClient(options, logger)
```

Apenas `store` e `sessionId` são obrigatórios; todo o resto tem um valor padrão sensato.

<h2 id="required-options">
  Opções obrigatórias
</h2>

<ParamField path="store" type="WaStore" required>
  A instância da store construída por [`createStore`](/pt-br/concepts/stores). Mantém cada domínio por sessão (auth, signal, app-state, …).
</ParamField>

<ParamField path="sessionId" type="string" required>
  Identificador lógico da sessão — ele indexa cada domínio dentro da `store`. Use uma string **estável** por dispositivo/conta. Trocá-lo entre execuções deixa as credenciais anteriores órfãs e força um novo pareamento.
</ParamField>

<h2 id="sessions-and-multi-tenancy">
  Sessões e multi-tenancy
</h2>

Como cada domínio da store é indexado por `sessionId`, uma única store pode conter muitas contas independentes. Para rodar várias contas em um processo, crie um `WaClient` por `sessionId` sobre a mesma store:

```ts theme={null}
const store = createStore({ /* ... */ })

const accountA = new WaClient({ store, sessionId: 'account-a' }, logger)
const accountB = new WaClient({ store, sessionId: 'account-b' }, logger)

await Promise.all([accountA.connect(), accountB.connect()])
```

Cada client pareia e reconecta de forma independente. Para o panorama completo — o que é por sessão vs compartilhado, a regra de single-writer entre processos, orçamento de memória, sharding e shutdown — veja [Deploys multi-sessão](/pt-br/guides/multi-session).

<h2 id="device-fingerprint">
  Fingerprint do dispositivo
</h2>

Estes controlam como o dispositivo aparece em **Dispositivos conectados** no celular:

<ParamField path="deviceBrowser" type="string" default="'chrome'">
  Id do navegador anunciado durante o pareamento (`'chrome'`, `'firefox'`, `'safari'`, …; veja `WA_BROWSERS`). Define o rótulo em *Dispositivos conectados*.
</ParamField>

<ParamField path="devicePlatform" type="string">
  Sobrescrita do id numérico de plataforma companion (`WA_COMPANION_PLATFORM_IDS`). Inferido de `deviceBrowser` quando omitido; defina explicitamente para plataformas não-navegador.
</ParamField>

```ts theme={null}
new WaClient({ store, sessionId: 'default', deviceBrowser: 'Chrome' }, logger)
```

<h2 id="history-sync">
  Sincronização de histórico
</h2>

<ParamField path="history" type="WaHistorySyncOptions">
  Controla o processamento dos chunks de `historySyncNotification` — tanto o bootstrap inicial que o WhatsApp envia após o pareamento quanto o backfill sob demanda disparado por [`message.requestHistorySync`](/pt-br/guides/receiving-messages#requesting-older-history).

  * `enabled?: boolean` — processa os chunks de histórico recebidos. **Padrão `true`.** Defina como `false` para descartá-los silenciosamente (útil quando você não persiste mailbox/threads/contacts e o download da conversa só gastaria banda). A lib ainda envia ack do chunk para o servidor parar de re-enviar, igual ao wa-web.
  * `requireFullSync?: boolean` — solicita o arquivo completo em vez de apenas os chats recentes.
</ParamField>

```ts theme={null}
new WaClient({
  store,
  sessionId: 'default',
  history: { enabled: true, requireFullSync: true }
}, logger)
```

O histórico chega como eventos [`history_sync_chunk`](/pt-br/concepts/events).

<h2 id="timeouts">
  Timeouts
</h2>

Todos em milissegundos; os padrões são ajustados para produção.

| Opção                            | Propósito                                                                  |
| -------------------------------- | -------------------------------------------------------------------------- |
| `iqTimeoutMs`                    | Timeout padrão para queries IQ (padrão 60s).                               |
| `nodeQueryTimeoutMs`             | Timeout padrão para chamadas brutas de `query()` de node.                  |
| `keepAliveIntervalMs`            | Intervalo entre os IQs de ping de keep-alive.                              |
| `deadSocketTimeoutMs`            | Quanto tempo sem resposta antes do socket ser considerado morto.           |
| `mediaTimeoutMs`                 | Timeout de upload/download de mídia.                                       |
| `appStateSyncTimeoutMs`          | Timeout de uma rodada de sincronização de app-state.                       |
| `messageAckTimeoutMs`            | Quanto tempo `message.send` espera pelo `<ack>` do servidor por tentativa. |
| `messageMaxAttempts`             | Máximo de tentativas para um único `message.send`.                         |
| `messageRetryDelayMs`            | Atraso entre as retentativas de envio de mensagem.                         |
| `signalFetchKeyBundlesTimeoutMs` | Timeout para buscas de key bundles (prekeys) do Signal.                    |

<h2 id="whatsapp-web-version">
  Versão do WhatsApp
</h2>

O zapo embarca uma versão de produção testada por transporte. Ocasionalmente o WhatsApp rejeita clients antigos durante o handshake noise com HTTP `405` / `failure_client_too_old`. Você tem três opções para se recuperar.

<ParamField path="version" type="string | () => string | Promise<string>">
  Sobrescreve a string de versão anunciada pelo client. Aceita um literal ou um resolver chamado **uma vez por `connect()`** — útil para buscar a versão atual sob demanda sem recriar o client. O formato aceito depende do transporte resolvido para o connect:

  * **Web** aceita uma versão de 3 a 5 partes (`2.3000.x[.y.z]`); as 4ª e 5ª partes, quando fornecidas, são anunciadas no payload noise.
  * **Mobile** aceita exatamente uma app version Android de 4 partes (`2.26.x.y`); ela sobrescreve `mobileTransport.deviceInfo.appVersion` no payload de login.

  Uma quantidade de partes inválida para o transporte resolvido lança no `connect()`.
</ParamField>

<ParamField path="recoverFromClientTooOld" type="boolean" default="false">
  Quando `true`, ao receber `failure_client_too_old` o client emite um warning, busca a versão atual para o transporte ativo ([`fetchLatestWaWebVersion()`](#fetchlatestwawebversion) para Web, [`fetchLatestWaMobileVersion()`](#fetchlatestwamobileversion) para Mobile), aplica como override de uso único e reconecta automaticamente. Em Mobile, o override é aplicado atualizando `deviceInfo.appVersion` para o próximo connect. Trate como paliativo até atualizar o zapo — o default embarcado continua sendo o caminho recomendado.
</ParamField>

```ts theme={null}
import { WaClient, fetchLatestWaWebVersion } from 'zapo-js'

// Fixa uma versão específica
new WaClient({ store, sessionId: 'default', version: '2.3000.1027421623' }, logger)

// Resolve sob demanda a cada connect()
new WaClient({
  store,
  sessionId: 'default',
  version: async () => (await fetchLatestWaWebVersion()).version
}, logger)

// Auto-recupera de HTTP 405 uma vez
new WaClient({ store, sessionId: 'default', recoverFromClientTooOld: true }, logger)
```

### `fetchLatestWaWebVersion()`

Faz scraping do `client_revision` atual em `web.whatsapp.com/sw.js` e retorna uma string de versão no formato `2.3000.x` aceito por `version` em uma sessão Web.

```ts theme={null}
import { fetchLatestWaWebVersion } from 'zapo-js'

const { version, parts } = await fetchLatestWaWebVersion({
  timeoutMs: 10_000,
  // Reaproveite o mesmo dispatcher usado para mídia / link-preview
  proxy: dispatcher
})
```

Opções: `timeoutMs` (padrão 10s), `proxy` (apenas dispatcher do undici — o `fetch` global não honra `http.Agent`), `signal`, `userAgent`, `headers` e uma override de `fetch` para testes. Erros de rede e parsing são lançados — envolva em `try`/`catch` se quiser cair no default embarcado.

### `fetchLatestWaMobileVersion()`

Faz scraping da versão atual do WhatsApp para Android em uma página pública de app-listing e retorna uma string `2.26.x.y` de 4 partes adequada para `version` em uma sessão Mobile (ou como override para `mobileTransport.deviceInfo.appVersion`).

```ts theme={null}
import { fetchLatestWaMobileVersion } from 'zapo-js'

const { version, parts } = await fetchLatestWaMobileVersion({
  timeoutMs: 10_000,
  proxy: dispatcher
})
```

Opções: tudo que o fetcher Web aceita (`timeoutMs`, `proxy`, `signal`, `userAgent`, `headers`, `fetch`) mais:

* `url?: string` — sobrescreve a página a ser lida. A fonte padrão é um mirror público de app-listing porque a própria página do WhatsApp em `whatsapp.com/android` só mostra a versão mínima obsoleta; re-aponte se o layout mudar ou for inalcançável da sua rede.
* `versionPattern?: RegExp` — sobrescreve o regex de extração. Precisa expor a versão no capture group 1. O padrão combina com um `2.x.x.x` de 4 partes e retorna o primeiro hit na página.

A string parseada precisa ter exatamente quatro partes numéricas; qualquer outra coisa lança (`invalid wa-mobile version parsed from page`). Erros de rede e parsing são lançados — envolva em `try`/`catch` se quiser cair em uma versão hardcoded conhecida.

<h2 id="presence-on-connect">
  Presença ao conectar
</h2>

<ParamField path="markOnlineOnConnect" type="boolean" default="false">
  * `false` (padrão) — anuncia como **indisponível**. Igual ao WhatsApp Web quando a aba não está em foco e mantém bots headless invisíveis por padrão. Com isso desligado, você continua recebendo notificações de mensagens enquanto está "offline".
  * `true` — anuncia o client como **online** (igual ao WhatsApp Web com a aba em foco no momento do login).
</ParamField>

<h2 id="passkey-gated-linking">
  Linking com passkey
</h2>

<ParamField path="signPasskeyAssertion" type="WaShortcakeAssertionSigner">
  Signer WebAuthn externo para o [handshake Shortcake de passkey](/pt-br/concepts/authentication#passkey-gated-linking-shortcake) forçado pelo servidor. Chamado com o `PublicKeyCredentialRequestOptions` bruto (`Uint8Array`) que o servidor emitiu; deve retornar `{ credentialId, webauthnAssertion }`. A source da credencial (authenticator real / virtual, relay) fica fora da lib.

  Sem isso, uma conta que receber um passkey prologue forçado pelo servidor emite [`auth_passkey_required`](/pt-br/concepts/events#auth--connection) com `hasSigner: false` e o link fica parado — veja o [deep dive](/pt-br/reverse-engineering/passkey-linking) de reverse engineering pro detalhe no nível do fio.
</ParamField>

<h2 id="addons-reactions-poll-votes">
  Addons (reações, votos em enquetes)
</h2>

<ParamField path="addons" type="{ autoDecrypt?: boolean, persistAllSecrets?: boolean }" default="{ autoDecrypt: true, persistAllSecrets: false }">
  [Addons](/pt-br/reference/glossary#addon) criptografados (votos em enquetes, reações, edições de mensagem, …) são descriptografados automaticamente e emitidos como eventos tipados [`message_addon`](/pt-br/guides/receiving-messages#addons). Defina `autoDecrypt: false` para recebê-los criptografados e descriptografar você mesmo via `client.message.tryDecryptAddon(event)`. O secret da mensagem-pai é procurado no cache `messageSecret` primeiro, depois no store `messages`.

  `persistAllSecrets: true` persiste o secret de 32 bytes de **toda** mensagem enviada e recebida, não só as de poll / event / bot-prompt que a lib sabe que terão follow-up. Addons criptografados cujo pai pode ser qualquer tipo de mensagem — reações, comentários, edições via `secretEncryptedMessage` — precisam do secret do pai para decifrar; sem essa flag, esses pais só continuam decifráveis depois de um restart quando o arquivo `messages` completo é persistente. Use para manter a decifragem possível guardando apenas o secret (`messages: 'none'`).

  Sem efeito quando o cache `messageSecret` é `'none'` — toda escrita de secret cai no noop store e é silenciosamente descartada. Com o provider `'memory'` default funciona pelo tempo de vida do processo, mas perde no restart e fica limitado ao LRU e ao TTL `messageSecretMs` do cache; aponte `messageSecret` para um backend persistente para manter os secrets entre restarts.
</ParamField>

<h2 id="media">
  Mídia
</h2>

<ParamField path="media" type="WaMediaOptions">
  Processamento de mídia. Passe um `processor` (de [`@zapo-js/media-utils`](/pt-br/installation#sending-media)) para gerar thumbnails/previews, medir dimensões e durações e construir waveforms de notas de voz antes do upload — e ligue/desligue cada etapa. Sem um processor a mídia ainda é enviada, só sem esse processamento. Veja o [guia de mídia](/pt-br/guides/media#media-processing) para a configuração completa.

  * `processor?: WaMediaProcessor` — a instância do processor
  * `generateThumbnail?: boolean` — thumbnails de preview de imagem/vídeo
  * `generateProbe?: boolean` — mede largura/altura/duração
  * `generateWaveform?: boolean` — waveform de nota de voz (PTT)
  * `generateStickerThumbnail?: boolean`
  * `normalizeVoiceNote?: boolean` — recodifica o áudio PTT para o formato que o WhatsApp espera
</ParamField>

<h2 id="link-previews">
  Previews de link
</h2>

<ParamField path="linkPreview" type="WaLinkPreviewOptions">
  Configuração global do fetcher embutido de preview de link, usado ao enviar texto que contém uma URL. Sobrescreva por mensagem com a [opção de envio](/pt-br/guides/sending-messages#send-options-reference) `linkPreview`.

  * `enabled?: boolean` — liga ou desliga globalmente a busca automática de preview de link
  * `fetchTimeoutMs?: number` — quanto esperar pela página de destino
  * `uploadHqThumbnail?: boolean` — envia uma thumbnail de preview em alta resolução
  * `allowPrivateHosts?: boolean` — permite buscar endereços privados/loopback (desligado por padrão, como proteção contra SSRF)
  * `maxHtmlBytes?: number` / `maxThumbnailBytes?: number` — limites de tamanho para o HTML e a imagem buscados
  * `userAgent?: string` — User-Agent enviado na busca
  * `proxy?: WaProxyTransport` — aplica proxy só a este fetcher (igual a [`proxy.linkPreview`](#proxy))
  * `fetcher?: WaLinkPreviewFetcher` — substitui o fetcher padrão por completo (ex.: seu próprio pipeline de scraping)
</ParamField>

<h2 id="chat-events">
  Eventos de chat
</h2>

<ParamField path="chatEvents" type="{ emitSnapshotMutations?: boolean }">
  Defina `emitSnapshotMutations: true` para reemitir eventos [`mutation`](/pt-br/concepts/events) para cada mudança vista durante uma sincronização de **snapshot** de app-state. Desligado por padrão, já que mutações de snapshot representam estado histórico, não mudanças ao vivo.
</ParamField>

<h2 id="write-behind-persistence">
  Persistência write-behind
</h2>

<ParamField path="writeBehind" type="WaWriteBehindOptions">
  Agrupa as mensagens recebidas em lotes antes de descarregá-las nas stores `messages` / `threads` / `contacts`.

  * `maxPendingKeys?: number`
  * `maxWriteConcurrency?: number`
  * `flushTimeoutMs?: number`
</ParamField>

<h2 id="proxy">
  Proxy
</h2>

<ParamField path="proxy" type="WaClientProxyOptions">
  Roteie cada perna através de um proxy de forma independente:

  * `ws` — a conexão WebSocket.
  * `mediaUpload` / `mediaDownload` — transferências de mídia.
  * `linkPreview` — o fetcher padrão de preview de link.
</ParamField>

Cada perna aceita um `WaProxyTransport`, que é:

* um **dispatcher do undici** (`WaProxyDispatcher`, por exemplo um `ProxyAgent` do undici) — usado para as pernas baseadas em `fetch` (mídia, preview de link), ou
* um **`Agent` `http`/`https` do Node** (`WaProxyAgent`) — usado para a perna WebSocket (`ws`).

O zapo escolhe a forma certa por perna automaticamente.

<Note>
  A perna `ws` requer o pacote [`ws`](/pt-br/installation#optional-peer-dependencies), porque o `WebSocket` nativo do runtime não consegue aceitar um `Agent` HTTP. Sem um proxy, nenhum pacote extra é necessário.
</Note>

<h3 id="http--https-proxy">
  Proxy HTTP / HTTPS
</h3>

Use um `ProxyAgent` do undici (um dispatcher) para as pernas de mídia/preview de link, e um `https-proxy-agent` (um `http.Agent`) para a perna `ws`:

```ts theme={null}
import { ProxyAgent } from 'undici'
import { HttpsProxyAgent } from 'https-proxy-agent'

const url = 'http://user:pass@proxy.example.com:8080' // ou https://…
const dispatcher = new ProxyAgent(url)
const wsAgent = new HttpsProxyAgent(url)

const client = new WaClient({
  store,
  sessionId: 'default',
  proxy: {
    ws: wsAgent,
    mediaUpload: dispatcher,
    mediaDownload: dispatcher,
    linkPreview: dispatcher
  }
}, logger)
```

<h3 id="socks-proxy">
  Proxy SOCKS
</h3>

Use `socks-proxy-agent` (funciona como um `http.Agent` para todas as pernas, incluindo `ws`):

```ts theme={null}
import { SocksProxyAgent } from 'socks-proxy-agent'

// socks5 (ou socks4) — host pode ser um domínio ou um IP
const agent = new SocksProxyAgent('socks5://user:pass@127.0.0.1:1080')

const client = new WaClient({
  store,
  sessionId: 'default',
  proxy: { ws: agent, mediaUpload: agent, mediaDownload: agent, linkPreview: agent }
}, logger)
```

<h3 id="ipv4-and-ipv6-hosts">
  Hosts IPv4 e IPv6
</h3>

O host do proxy pode ser um domínio ou um literal de IP. **Endereços IPv6 devem ser envolvidos em colchetes**:

```ts theme={null}
// IPv4
new ProxyAgent('http://203.0.113.10:8080')
new SocksProxyAgent('socks5://203.0.113.10:1080')

// IPv6 — coloque o endereço entre colchetes
new ProxyAgent('http://[2001:db8::1]:8080')
new SocksProxyAgent('socks5://[2001:db8::1]:1080')

// Com credenciais
new ProxyAgent('http://user:pass@[2001:db8::1]:8080')
```

<Tip>
  Aponte para o proxy apenas as pernas que você precisar — por exemplo, defina só `ws` para tunelar a conexão deixando a mídia transferir diretamente, ou vice-versa.
</Tip>

<h2 id="logout-store-clearing">
  Limpeza da store no logout
</h2>

<ParamField path="logoutStoreClear" type="WaLogoutStoreClearOptions">
  Controle por domínio do que o [`logout()`](/pt-br/concepts/authentication#logging-out) apaga.

  Por padrão, o **arquivo da mailbox** (`messages`, `threads`, `contacts`) é **preservado** para que o usuário mantenha seu histórico ao reparear. Todos os outros domínios (credenciais, estado Signal, app-state, caches, privacy tokens) são **limpos** para começar o próximo pareamento do zero. `true` / `false` explícitos sempre vencem o padrão.

  ```ts theme={null}
  // Preserva tudo exceto auth (re-pareia sem mexer no estado)
  logoutStoreClear: { signal: false, appState: false }

  // Apaga também a mailbox (reset completo)
  logoutStoreClear: { messages: true, threads: true, contacts: true }
  ```
</ParamField>

<h2 id="logging">
  Logging
</h2>

O `WaClient` aceita um `Logger` como segundo argumento do construtor. Se você omitir, um `ConsoleLogger('info')` padrão é usado. Níveis, do mais baixo ao mais alto: `trace`, `debug`, `info`, `warn`, `error`.

Duas implementações vêm com o pacote.

### ConsoleLogger

Sem dependências. Escreve registros estruturados em `console.log` / `console.warn` / `console.error`. Bom para desenvolvimento, testes e funções serverless onde você não consegue adicionar um transport de logger.

```ts theme={null}
import { ConsoleLogger } from 'zapo-js'

const client = new WaClient(options, new ConsoleLogger('info'))
```

### createPinoLogger

Factory async que carrega dinamicamente o [`pino`](https://github.com/pinojs/pino) (e o `pino-pretty` quando `pretty: true`), configura e envolve no adapter `PinoLogger`. Lança `optional dependency "pino" is not installed` quando o pino não está presente — instale com `npm i pino pino-pretty`.

```ts theme={null}
import { createPinoLogger } from 'zapo-js'

const logger = await createPinoLogger({ level: 'info', pretty: true })
const client = new WaClient(options, logger)
```

| Campo           | Tipo                              | Descrição                                                                                                                   |
| --------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `level`         | `LogLevel`                        | Nível mínimo a emitir. Default `'info'`.                                                                                    |
| `name`          | `string`                          | Nome da instância pino anexado a cada registro.                                                                             |
| `base`          | `Record<string, unknown> \| null` | Bindings base mesclados em cada registro. Passe `null` para descartar `pid`/`hostname` padrões do pino.                     |
| `pinoOptions`   | `Record<string, unknown>`         | Passthrough para `pino()` para qualquer coisa que não esteja exposta acima (redação, serializers customizados, …).          |
| `pretty`        | `boolean`                         | Quando `true`, conecta o `pino-pretty` como transport. Mantenha `false` (default) em produção para emitir linhas JSON.      |
| `prettyOptions` | `PinoPrettyOptions`               | Encaminhado pro transport `pino-pretty` — veja as [opções do `pino-pretty`](https://github.com/pinojs/pino-pretty#options). |

### PinoLogger (traga o seu Pino)

Se você já configura Pino centralmente — child loggers, transports customizados, destinos em arquivo — construa `PinoLogger` diretamente para envolver sua instância existente. A factory é uma conveniência; a classe é o adapter de fato, e usá-la evita o import dinâmico do `pino`.

```ts theme={null}
import pino from 'pino'
import { PinoLogger } from 'zapo-js'

const root = pino({ name: 'my-app', transport: { /* ... */ } })
const child = root.child({ component: 'whatsapp' })

const client = new WaClient(options, new PinoLogger(child, 'info'))
```

A assinatura é `new PinoLogger(logger, level = 'info')`. O nível é encaminhado pra `logger.level` e usado como o `level` reportado pelo adapter.

<h3 id="custom-logger">
  Logger customizado
</h3>

Precisa de um sink que as implementações nativas não cobrem — Datadog, OpenTelemetry, syslog, um pipeline de observabilidade interno? Implemente a interface `Logger` e passe a instância pro `WaClient`. A interface é pequena:

```ts theme={null}
import type { Logger, LogLevel } from 'zapo-js'

interface Logger {
  readonly level: LogLevel
  trace(message: string, context?: Readonly<Record<string, unknown>>): void
  debug(message: string, context?: Readonly<Record<string, unknown>>): void
  info(message: string, context?: Readonly<Record<string, unknown>>): void
  warn(message: string, context?: Readonly<Record<string, unknown>>): void
  error(message: string, context?: Readonly<Record<string, unknown>>): void
  /**
   * Retorna um logger derivado que pre-vincula `bindings` no contexto de cada
   * chamada. Os bindings empilham: `parent.child(a).child(b)` mescla `{ ...a, ...b }`.
   * Em conflito de chave, o contexto da chamada vence.
   */
  child(bindings: Readonly<Record<string, unknown>>): Logger
}
```

`LogLevel` é `'trace' | 'debug' | 'info' | 'warn' | 'error'`. A biblioteca chama os cinco métodos de level diretamente — não há uma camada de level-gating à frente, então sua implementação é responsável por filtrar contra `this.level` se quiser pular chamadas baratas.

Um exemplo mínimo que encaminha para um sink externo e rastreia bindings por `child()`:

```ts theme={null}
import type { Logger, LogLevel } from 'zapo-js'

const LEVEL_RANK: Record<LogLevel, number> = {
  trace: 10, debug: 20, info: 30, warn: 40, error: 50
}

class MyLogger implements Logger {
  constructor(
    public readonly level: LogLevel = 'info',
    private readonly bindings: Readonly<Record<string, unknown>> = {}
  ) {}

  private write(at: LogLevel, message: string, context?: Readonly<Record<string, unknown>>): void {
    if (LEVEL_RANK[at] < LEVEL_RANK[this.level]) return
    sendToObservability({ level: at, message, ...this.bindings, ...context })
  }

  trace(message: string, context?: Readonly<Record<string, unknown>>) { this.write('trace', message, context) }
  debug(message: string, context?: Readonly<Record<string, unknown>>) { this.write('debug', message, context) }
  info(message: string, context?: Readonly<Record<string, unknown>>)  { this.write('info',  message, context) }
  warn(message: string, context?: Readonly<Record<string, unknown>>)  { this.write('warn',  message, context) }
  error(message: string, context?: Readonly<Record<string, unknown>>) { this.write('error', message, context) }

  child(bindings: Readonly<Record<string, unknown>>): Logger {
    return new MyLogger(this.level, { ...this.bindings, ...bindings })
  }
}

const client = new WaClient(options, new MyLogger('info'))
```

<Note>
  `child()` é usado internamente para anexar bindings por componente (ex.: `{ component: 'noise' }`, `{ component: 'signal', sessionId }`). Retornar uma nova instância com bindings mesclados — em vez de mutar — mantém esses tags com escopo no subsistema que os produziu.
</Note>

<h2 id="plugins">
  Plugins
</h2>

<ParamField path="plugins" type="readonly WaClientPluginDefinition[]">
  Plugins opcionais de `WaClient` — hooks de comportamento e/ou coordinators expostos em `client[exposeAs]`. Autorados com [`defineWaClientPlugin`](/pt-br/concepts/plugins). O plugin de chamadas de voz (`@zapo-js/voip`) é a implementação de referência; veja a página do [sistema de plugins](/pt-br/concepts/plugins) para saber como conectar e autorar plugins.
</ParamField>

```ts theme={null}
import { voipPlugin } from '@zapo-js/voip'

new WaClient({
  store,
  sessionId: 'default',
  plugins: [voipPlugin()]
}, logger)
```

<h2 id="advanced-options">
  Opções avançadas
</h2>

Raramente necessárias — listadas para completude.

* `chatSocketUrls?: readonly string[]` — sobrescreve a lista de endpoints WebSocket de chat do WhatsApp (ex.: rotear por um servidor falso em testes, ou fixar um edge específico).
* `privacyToken?: WaPrivacyTokenOptions` — ajusta a emissão de trusted-contact-token (TC token): durações e número de buckets.
* `testHooks?: WaClientTestHooks` — fixtures só para testes (ex.: um root CA Noise customizado). Eles **não** burlam nenhuma verificação de segurança; para realmente pular uma checagem, use as opções `dangerous` abaixo.

<h2 id="dangerous-options">
  Opções perigosas
</h2>

<Danger>
  As flags `dangerous` desabilitam, cada uma, uma verificação de segurança que o caminho de produção aplica (verificação de assinatura, checagens de MAC do app-state, …). Elas existem para testes contra um servidor falso. **Nunca as habilite em produção.**
</Danger>
