PEPINO ZAPdocs
Comece aqui

Sua primeira instância

Ciclo de vida de uma instância no Pepino Zap — criar, parear com o WhatsApp por QR Code (polling ou SSE), consultar, remover e tratar os erros de cada etapa.

Ver como Markdown

Uma instância representa uma conexão com o WhatsApp dentro do seu Projeto — na prática, um número de telefone conectado. Cada instância tem um id próprio (prefixo inst_), um status que descreve em que ponto do ciclo de vida ela está, e, depois de pareada, o número (phoneNumber) e o jid (o endereço do contato no WhatsApp). Você pode ter mais de uma instância por Projeto, até o limite de números do seu plano.

Este guia cobre o ciclo de vida completo: criar a instância, parear com o WhatsApp lendo o QR Code (por polling — recomendado — ou por Server-Sent Events), consultar o estado atual, remover e tratar os erros de cada etapa.

Autenticação e modos

Todas as requisições exigem o header Authorization: Bearer <API_KEY>. A única exceção é o endpoint de QR via SSE, que autentica via ?token=<API_KEY> na query string, porque o EventSource do navegador não envia headers (detalhado em Alternativa: stream SSE). O endpoint de QR via polling usa o header normalmente.

O host decide o modo, e a key precisa casar com ele:

ModoHostPrefixo da key
Produçãohttps://api.pepinozap.com.brpzk_live_
Testehttps://sandbox.pepinozap.com.brpzk_test_

Uma key de teste contra o host de produção (ou vice-versa) retorna 401. Os exemplos abaixo usam a URL de produção; troque o host e o prefixo da key para validar no sandbox.

Instâncias são isoladas por modo: uma instância criada no teste não aparece nem responde nas chamadas de produção, e vice-versa. Tentar acessar uma instância do outro modo retorna 404 (como se ela não existisse) — não é um vazamento, é o isolamento esperado.

Ciclo de vida

O status da instância é um enum de seis valores:

StatusSignificadoOperacional?
DISCONNECTEDEstado inicial após a criação e sempre que a conexão não está ativa.Não
CONNECTINGA instância está abrindo a conexão com o WhatsApp.Não
QR_PENDINGAguardando o pareamento. Um QR Code está disponível para leitura.Não
CONNECTEDPareada e operacional. Mensagens podem ser enviadas.Sim
BANNEDNúmero bloqueado pela plataforma do WhatsApp. Estado terminal.Não
ERRORFalha irrecuperável. Estado terminal.Não

O caminho feliz, do zero ao número operando, percorre os quatro primeiros em ordem:

DISCONNECTED  →  CONNECTING  →  QR_PENDING  →  CONNECTED

Logo após a criação, a instância nasce em DISCONNECTED. Quando você abre o fluxo de pareamento (qualquer um dos endpoints de QR abaixo), ela passa por CONNECTING e chega a QR_PENDING, expondo o QR Code. Assim que o QR é lido no aparelho, ela vai para CONNECTED — e só então aceita envio de mensagens.

CONNECTED = pareamento concluído de verdade. A instância só entra em CONNECTED depois que o QR é escaneado e o WhatsApp confirma o vínculo (o evento interno PairSuccess) — nunca só por estar abrindo a conexão para exibir o QR. Por isso, ao ver CONNECTED (no GET /v1/instances/{id} ou no webhook instance.status), pode confiar: a sessão está ativa e o número pronto para enviar. Enquanto o QR não é lido, a instância fica em QR_PENDING/DISCONNECTED, não em CONNECTED.

Uma vez pareada, a instância reconecta automaticamente após quedas, reinícios ou deploys, sem exigir novo QR Code — a sessão é persistida. A reconexão automática não ocorre nos estados terminais BANNED (número bloqueado pelo WhatsApp) e ERROR (falha irrecuperável). Se o número for deslogado pelo próprio usuário no aparelho (em "Aparelhos conectados"), a instância retorna para DISCONNECTED e um novo pareamento é necessário.

Enviar mensagem para uma instância em BANNED ou ERROR retorna 409 (instance is not operational) — o envio nem entra na fila. Para uma instância em DISCONNECTED, CONNECTING ou QR_PENDING, o envio é aceito, mas só sai depois que ela chegar a CONNECTED. Confirme status: "CONNECTED" antes de disparar mensagens.

Listar instâncias

Para ver todas as instâncias do Projeto no modo atual:

curl https://api.pepinozap.com.br/v1/instances \
  -H "Authorization: Bearer $YOUR_API_KEY"

Resposta 200 OK com um array (vazio [] se você ainda não criou nenhuma):

[
  {
    "id": "inst_abc123",
    "projectId": "proj_xyz789",
    "name": "minha-instancia",
    "status": "CONNECTED",
    "phoneNumber": "5511999999999",
    "jid": "5511999999999@s.whatsapp.net",
    "settings": {
      "rejectCalls": false,
      "rejectCallMessage": "",
      "ignoreGroups": false,
      "alwaysOnline": false,
      "readReceipts": false
    },
    "createdAt": "2026-05-30T12:00:00Z",
    "updatedAt": "2026-05-30T12:00:00Z"
  }
]

A lista traz apenas as instâncias do modo da key usada (teste ou produção).

Criar uma instância

O único campo do corpo é o name:

CampoTipoObrigatórioDescrição
namestringSimUm rótulo seu para identificar a instância no painel e nas listagens. Pode repetir entre instâncias (não é único).
curl -X POST https://api.pepinozap.com.br/v1/instances \
  -H "Authorization: Bearer $YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "minha-instancia"}'

Resposta 201 Created:

{
  "id": "inst_abc123",
  "projectId": "proj_xyz789",
  "name": "minha-instancia",
  "status": "DISCONNECTED",
  "phoneNumber": null,
  "jid": null,
  "settings": {
    "rejectCalls": false,
    "rejectCallMessage": "",
    "ignoreGroups": false,
    "alwaysOnline": false,
    "readReceipts": false
  },
  "createdAt": "2026-05-30T12:00:00Z",
  "updatedAt": "2026-05-30T12:00:00Z"
}

O que cada campo significa logo na criação:

  • idguarde este valor. É ele que identifica a instância em todas as demais chamadas (QR, consulta, envio, configurações, remoção).
  • status — sempre DISCONNECTED no nascimento; só muda quando você inicia o pareamento.
  • phoneNumber e jid — vêm null enquanto a instância não estiver pareada. São preenchidos automaticamente após o CONNECTED (o phoneNumber é o número em formato E.164 sem +, e o jid é o endereço completo no WhatsApp, ex.: 5511999999999@s.whatsapp.net).
  • settings — começa com todas as opções desligadas (ver Configurações da instância).

Criar a instância exige um plano ativo no Projeto (em produção). Sem assinatura, este endpoint retorna 402 (PAYMENT_REQUIRED) — assine em Meu plano, no painel. No modo teste, a criação é grátis e self-service (não exige plano). O pareamento por QR Code abaixo não exige plano em nenhum dos modos. Ver Plano ativo.

Erros ao criar

O envelope de erro é { "error": { "code", "message" } }.

CódigoQuando acontece
400Corpo inválido — JSON malformado ou name ausente/vazio.
401API key ausente ou inválida (inclui key do modo errado para o host).
402O Projeto não tem plano ativo (PAYMENT_REQUIRED). Assine em Meu plano.
409Cota de números do plano atingida. Contrate mais um número em Meu plano. (Não é nome duplicado — nomes podem repetir.)

Parear via QR Code

O pareamento conecta o número do WhatsApp à instância. O fluxo é: você abre um dos endpoints de QR, isso pede ao serviço para gerar um QR Code na hora, você renderiza esse código na sua interface, e o usuário o lê no aparelho em WhatsApp → Aparelhos conectados → Conectar um aparelho. Assim que o aparelho confirma, a instância vai para CONNECTED.

duas formas de obter o QR Code. Recomendamos o polling (GET /v1/instances/{id}/qr/poll): cada requisição é curta e atravessa antivírus e proxies corporativos sem problemas. O stream SSE (GET /v1/instances/{id}/qr) é uma alternativa, porém alguns antivírus e proxies bufferizam streams e impedem a entrega do QR Code.

O QR Code expira em segundos e é rotacionado — um novo código é emitido periodicamente enquanto a instância está em QR_PENDING. Por isso, sempre renderize o código mais recente que você receber (no polling, o qr.code da última resposta; no SSE, o último evento qr). Não fixe o primeiro QR na tela.

Polling (recomendado)

Faça GET /v1/instances/{id}/qr/poll a cada ~2 segundos. Cada chamada faz duas coisas: pede ao worker que conecte a instância agora (idempotente — não faz nada se já houver conexão, e revive o QR se o ciclo anterior expirou) e devolve o snapshot atual.

O corpo da resposta (200 OK) tem dois campos:

CampoTipoDescrição
connectedbooleantrue quando a instância já está conectada (pareamento concluído — pare o polling).
qrobjeto { code, at } ou nullEnquanto não conectada, traz o QR atual: code (a string para renderizar como QR Code) e at (timestamp de emissão). Vem null quando ainda não há QR disponível ou quando já está conectada.

Exemplos de resposta — antes e depois do pareamento:

// ainda aguardando leitura
{
  "connected": false,
  "qr": {
    "code": "2@A1b2C3d4E5f6...",
    "at": "2026-05-30T12:00:03Z"
  }
}
// pareamento concluído — pare o polling
{
  "connected": true,
  "qr": null
}
const id = 'inst_abc123';
const apiKey = 'pzk_live_...';

const timer = setInterval(async () => {
  const res = await fetch(`https://api.pepinozap.com.br/v1/instances/${id}/qr/poll`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  const { connected, qr } = await res.json();

  if (connected) {
    clearInterval(timer); // pareamento concluído
    return;
  }
  if (qr) {
    // Renderize "qr.code" como QR Code usando uma biblioteca de QR.
    renderQrCode(qr.code);
  }
}, 2000);

Diferente do SSE, o polling usa o header Authorization: Bearer <API_KEY> normalmente. Como cada requisição é curta e independente, ele funciona mesmo em redes com antivírus (Avast, Kaspersky, ESET) ou proxies corporativos que bufferizam streams — a causa mais comum de "o QR Code não aparece".

Alternativa: stream SSE

O endpoint GET /v1/instances/{id}/qr transmite Server-Sent Events (Content-Type: text/event-stream). Em vez de você consultar repetidamente, o servidor empurra os eventos conforme acontecem. Os eventos são:

EventoPayloadDescrição
ready{ "instanceId": "inst_..." }Evento inicial, indicando que o stream foi aberto. Aguarde o primeiro qr em seguida.
qr{ "code", "at" }Cada QR novo emitido. Renderize o campo code como um QR Code para leitura.
status{ "status", "jid?", "reason?", "at" }Emitido a cada transição de estado. Veja o vocabulário abaixo.
ping(vazio)Keepalive, emitido aproximadamente a cada 25s para manter a conexão viva através de proxies. Ignore-o.

Pelo SSE, o campo status usa o vocabulário "rico" do worker — diferente e maior que o enum de 6 valores acima. Trate esse vocabulário, não o enum:

status (SSE)SignificadoComo tratar
CONNECTEDConexão estabelecida.Sucesso — feche o stream.
PAIREDPareou (equivale a conectado).Sucesso — feche o stream.
DISCONNECTEDConexão caiu (pode reconectar sozinha se já pareada).Aguarde ou gere novo QR.
LOGGED_OUTDeslogado (ex.: removido no aparelho).Falha — exige novo pareamento.
STREAM_REPLACEDOutra sessão assumiu a conexão.Falha — encerre.
TEMPORARY_BANBloqueio temporário do número pela plataforma.Falha — encerre.
PAIR_TIMEOUTNinguém escaneou o QR a tempo.Falha — gere um novo QR.
PAIR_ERRORErro durante o pareamento.Falha — gere um novo QR.

Trate PAIRED/CONNECTED como sucesso e PAIR_TIMEOUT/PAIR_ERROR/LOGGED_OUT/STREAM_REPLACED/TEMPORARY_BAN como falha/encerramento. Não faça um switch estrito só nos 6 valores do enum de status: o stream pareceria "travado" esperando um CONNECTED que, num timeout/erro, nunca chega. O polling acima evita essa complexidade — ele só expõe connected: true/false.

Abra o stream, renderize o code do evento qr e, quando o evento status reportar CONNECTED ou PAIRED, o pareamento foi concluído.

Token efêmero (não exponha a API key na URL)

Como o EventSource autentica pela query string (?token=), passar a API key crua na URL a expõe em logs e no histórico do navegador. Para evitar isso, emita um token efêmero com POST /v1/instances/{id}/qr-token e use-o no ?token=.

curl -X POST https://api.pepinozap.com.br/v1/instances/$INSTANCE_ID/qr-token \
  -H "Authorization: Bearer $YOUR_API_KEY"

Resposta 200 OK:

{ "token": "eyJhbGciOiJI..." }

O token é válido por ~2 minutos e serve apenas para abrir o stream de QR dessa instância — não dá acesso ao resto da API. Se o pareamento demorar mais que isso, emita um novo token e reabra o stream.

const id = 'inst_abc123';

// Emita o token efêmero (em vez de expor a API key na URL).
const tokenRes = await fetch(`https://api.pepinozap.com.br/v1/instances/${id}/qr-token`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});
const { token } = await tokenRes.json();

const url = `https://api.pepinozap.com.br/v1/instances/${id}/qr?token=${token}`;
const source = new EventSource(url);

source.addEventListener('ready', () => {
  // Stream aberto; aguardando o primeiro evento "qr".
});

source.addEventListener('qr', (event) => {
  const { code } = JSON.parse(event.data);
  // Renderize "code" como QR Code usando uma biblioteca de QR.
  renderQrCode(code);
});

source.addEventListener('status', (event) => {
  const { status } = JSON.parse(event.data);
  if (status === 'CONNECTED' || status === 'PAIRED') {
    source.close(); // pareado com sucesso
  } else if (['PAIR_TIMEOUT', 'PAIR_ERROR', 'LOGGED_OUT', 'STREAM_REPLACED', 'TEMPORARY_BAN'].includes(status)) {
    source.close(); // falhou/encerrou — gere um novo QR ou trate o erro
  }
});

O EventSource do navegador não envia o header Authorization. Por isso, o endpoint SSE de QR autentica pela query string ?token=<API_KEY> (ou pelo token efêmero do qr-token). Em todos os demais endpoints, use sempre Authorization: Bearer <API_KEY>.

Erros ao parear

CódigoEndpointQuando acontece
401qr/poll, qr-tokenAPI key ausente ou inválida (ou do modo errado para o host).
401qr (SSE)token ausente, inválido ou expirado na query string.
404todosInstância não encontrada — não existe, não é do seu Projeto, ou é do outro modo (teste/produção).

O pareamento não exige plano ativo — você pode gerar e ler o QR Code mesmo sem assinatura. O que exige plano é criar a instância e enviar mensagens.

Consultar e remover

Consultar

Verifique o estado atual da instância a qualquer momento — é assim que você confirma se o pareamento chegou ao CONNECTED e lê o phoneNumber/jid preenchidos:

curl https://api.pepinozap.com.br/v1/instances/$INSTANCE_ID \
  -H "Authorization: Bearer $YOUR_API_KEY"

Resposta 200 OK com a instância completa:

{
  "id": "inst_abc123",
  "projectId": "proj_xyz789",
  "name": "minha-instancia",
  "status": "CONNECTED",
  "phoneNumber": "5511999999999",
  "jid": "5511999999999@s.whatsapp.net",
  "settings": {
    "rejectCalls": false,
    "rejectCallMessage": "",
    "ignoreGroups": false,
    "alwaysOnline": false,
    "readReceipts": false
  },
  "createdAt": "2026-05-30T12:00:00Z",
  "updatedAt": "2026-05-30T12:05:00Z"
}

Consultar não exige plano ativo — leitura é sempre liberada.

Remover

Para remover uma instância (retorna 204 No Content, sem corpo):

curl -X DELETE https://api.pepinozap.com.br/v1/instances/$INSTANCE_ID \
  -H "Authorization: Bearer $YOUR_API_KEY"

A remoção não exige plano ativo. Ela desfaz o pareamento e libera a vaga de número na sua cota — depois disso, o id não responde mais (uma consulta posterior retorna 404). Se você quiser reconectar o mesmo número, crie uma nova instância e pareie de novo.

Tanto consultar quanto remover retornam 404 se o id não existir, for de outro Projeto ou for do outro modo (uma key de teste não enxerga uma instância de produção, e vice-versa). Se você está certo de que o id existe e mesmo assim recebe 404, confira se o host e o prefixo da key batem com o modo da instância.

Configurações da instância

Cada instância tem um conjunto de configurações de comportamento — pequenos liga/desliga que mudam como o número conectado reage a chamadas, grupos, presença e leitura. São totalmente opcionais: por padrão todas vêm desligadas (o comportamento neutro do WhatsApp), e você liga só as que fizerem sentido para o seu caso.

São cinco configurações, resumidas aqui e detalhadas logo abaixo:

CampoTipoPadrãoEm uma frase
rejectCallsbooleanfalseRejeita automaticamente chamadas (voz/vídeo) recebidas.
rejectCallMessagestring""Mensagem enviada a quem ligou, logo após a rejeição (só com rejectCalls: true).
ignoreGroupsbooleanfalseIgnora mensagens de grupo (sem webhook, sem WebSocket, sem conversa no CRM).
alwaysOnlinebooleanfalseMantém o número aparecendo "online" enquanto conectado.
readReceiptsbooleanfalseMarca mensagens recebidas como lidas automaticamente (tique azul para o remetente).
importHistorybooleanfalseAo parear, importa o histórico de conversas que o WhatsApp libera (persistido, sem mídia). Desligado, começa do zero.

Como atualizar

Use PATCH /v1/instances/{id}/settings. É um patch parcial: envie apenas os campos que quer alterar — os que você omitir permanecem com o valor atual (não são zerados). Para ligar só uma opção, mande só ela.

curl -X PATCH https://api.pepinozap.com.br/v1/instances/$INSTANCE_ID/settings \
  -H "Authorization: Bearer $YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rejectCalls": true,
    "rejectCallMessage": "No momento não atendemos chamadas. Envie uma mensagem que respondemos por aqui.",
    "ignoreGroups": true,
    "alwaysOnline": false,
    "readReceipts": true
  }'

A resposta é 200 OK com a instância completa, já trazendo o objeto settings atualizado:

{
  "id": "inst_abc123",
  "projectId": "proj_xyz789",
  "name": "minha-instancia",
  "status": "CONNECTED",
  "phoneNumber": "5511999999999",
  "jid": "5511999999999@s.whatsapp.net",
  "settings": {
    "rejectCalls": true,
    "rejectCallMessage": "No momento não atendemos chamadas. Envie uma mensagem que respondemos por aqui.",
    "ignoreGroups": true,
    "alwaysOnline": false,
    "readReceipts": true
  },
  "createdAt": "2026-05-30T12:00:00Z",
  "updatedAt": "2026-06-05T12:00:00Z"
}

As mudanças são persistidas e aplicadas ao número em poucos segundos (no próximo ciclo de sincronização do worker). Continuam valendo após quedas, reconexões e deploys — você não precisa reenviar a cada conexão.

O que cada configuração faz

rejectCalls — rejeitar chamadas (boolean, padrão false)

Quando true, toda chamada de voz ou vídeo recebida é rejeitada automaticamente, na hora, sem tocar. Ideal para números que são só de mensagens (atendimento, notificações). Quando false, as chamadas tocam normalmente. Para avisar quem ligou, combine com rejectCallMessage.

rejectCallMessage — resposta automática à chamada (string, padrão "")

Texto enviado automaticamente como mensagem para quem ligou, logo depois de a chamada ser rejeitada. Só tem efeito quando rejectCalls: true (é ignorado se as chamadas não estão sendo rejeitadas). Deixe vazio para rejeitar sem mandar nada.

Exemplo prático: ligue rejectCalls e defina rejectCallMessage como "No momento não atendemos chamadas. Envie uma mensagem que respondemos por aqui." — assim, quem tentar ligar recebe a orientação na mesma hora.

ignoreGroups — ignorar grupos (boolean, padrão false)

Quando true, mensagens recebidas em grupos são ignoradas pela plataforma: não disparam webhook nem WebSocket, e não criam nem atualizam conversa no CRM (Inbox). Use quando você só quer tratar conversas individuais (1:1) e não ser inundado pelo tráfego de grupos. Não afeta mensagens que você envia para grupos pela API. Quando false, os grupos são processados normalmente.

alwaysOnline — sempre online (boolean, padrão false)

Quando true, o número aparece sempre "online" para os contatos enquanto a instância estiver conectada (a presença "disponível" é reanunciada a cada conexão). Quando false, a presença segue o comportamento normal do WhatsApp. Não altera a entrega de mensagens — muda só a indicação de presença que os contatos veem.

readReceipts — confirmação de leitura automática (boolean, padrão false)

Quando true, cada mensagem recebida é marcada como lida automaticamente assim que a plataforma a processa — o remetente vê o "lido" (tique azul). Quando false, as mensagens não são marcadas como lidas (o remetente não vê o tique azul), mesmo que você já as tenha recebido pelo webhook. Ligue se quer dar confirmação de leitura automática aos contatos.

importHistory — importar histórico ao conectar (boolean, padrão false)

Quando true, ao parear a instância a plataforma importa o histórico de conversas que o WhatsApp libera no pareamento e o persiste — você passa a lê-lo pelos endpoints de Inbox. Quando false (padrão), a instância começa do zero: captura só as mensagens novas dali pra frente (conecta e carrega mais rápido). Para trazer conversas anteriores sob demanda depois (sem ligar o import no pareamento), use POST /v1/inbox/conversations/{id}/history — ele puxa o lote anterior de uma conversa específica.

Limite do WhatsApp: mesmo ligado, não é "todo o histórico desde sempre" — o WhatsApp envia uma janela recente para um aparelho recém-pareado. O histórico importado entra sem mídia (só texto/metadados; os anexos antigos não são baixados, para não pesar), e as mensagens importadas não disparam webhook (message.received) — elas só populam o histórico do Inbox.

Como desligar uma configuração

Como é patch parcial, para desligar uma opção envie-a com o valor desligado — false (booleanos) ou "" (o texto de rejeição):

curl -X PATCH https://api.pepinozap.com.br/v1/instances/$INSTANCE_ID/settings \
  -H "Authorization: Bearer $YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "rejectCalls": false, "rejectCallMessage": "" }'

Erros possíveis

O envelope de erro é { "error": { "code", "message" } }.

  • 400 — corpo JSON inválido (por exemplo, um campo com o tipo errado).
  • 401 — API key ausente ou inválida.
  • 403 — projeto inativo.
  • 404 — instância não encontrada (não existe, não é do seu Projeto, ou é de outro modo — teste/produção).

A referência completa, com todos os campos e o playground "Try it", está em API Reference: configurações da instância.

Erros por endpoint

Todos os erros usam o envelope { "error": { "code", "message" } }. Resumo por etapa deste guia:

CódigoSignificadoOnde aparece
400Corpo inválido (JSON malformado, name ausente, tipo errado).criar, configurações
401API key ausente/inválida, ou token SSE inválido/expirado.todos
402PAYMENT_REQUIRED — Projeto sem plano ativo. Assine em Meu plano. Ver Plano ativo.criar (produção), enviar
403Projeto inativo ou sem permissão.configurações, e demais
404Instância não encontrada (não existe, é de outro Projeto ou de outro modo).consultar, remover, QR, configurações
409Cota de números do plano atingida ao criar; ou instância não operacional (BANNED/ERROR) ao enviar.criar, enviar

Próximos passos

On this page