PEPINO ZAPdocs
Comece aqui

Persistência e histórico

O PEPINO ZAP é o system of record das suas conversas — você não precisa de banco nem storage próprios. Entenda histórico persistido vs. efêmero, como ler pela API e o impacto no Inbox.

Ver como Markdown

O PEPINO ZAP persiste todo o histórico das suas conversas (texto e mídia) na nossa infraestrutura. Para o integrador isso significa uma coisa direta: você não precisa manter um banco de dados nem um storage de mídia próprios só para o WhatsApp. Nós somos o system of record; você lê o histórico pela API quando precisar.

Esta página explica o que é guardado, a diferença entre histórico persistido e efêmero, como ler o histórico pela API e o impacto no Inbox/conversas. Os exemplos usam a URL de produção https://api.pepinozap.com.br; no modo teste, troque para https://sandbox.pepinozap.com.br (com uma key pzk_test_). Todas as requisições exigem o header Authorization: Bearer <API_KEY>, e a key precisa casar com o host — ver Autenticação.

O que guardamos

Tudo que passa pelo seu número conectado é gravado e fica vinculado ao seu Projeto, escopado por modo (teste/produção):

DadoOnde ficaVocê precisa guardar?
Conversas (uma por contato)Nosso PostgresNão
Mensagens (enviadas e recebidas)Nosso PostgresNão
ContatosNosso PostgresNão
Mídia recebida (imagem, áudio, vídeo, documento, sticker)Nosso storage (S3)Não

Teste e produção são separados. O histórico de uma key pzk_test_ (host sandbox.*) e o de uma key pzk_live_ (host api.*) vivem em espaços isolados. Uma conversa criada em teste não aparece nas consultas de produção, e vice-versa. Veja Modos: teste e produção.

Retenção de mídia: 2 anos. O texto das mensagens fica no histórico indefinidamente; o binário da mídia (imagem, áudio, vídeo, documento, sticker) é guardado por 2 anos e, depois disso, é removido automaticamente da nossa infraestrutura — a mensagem permanece (texto e metadados), mas sem o arquivo. Se você precisa da mídia por mais tempo, baixe e armazene no seu lado quando o evento chega (a mediaUrl é pré-assinada e expira; ver Eventos).

O histórico sobrevive a deletar a instância

Apagar (ou desconectar) uma instância remove apenas a sessão do WhatsApp daquele número — o histórico permanece:

AçãoO que acontece
Desconectar a instânciaNada é apagado. A instância e a sessão ficam; reconecta sozinha. Histórico intacto.
Deletar a instânciaRemove a sessão (pareamento) do WhatsApp. As conversas, mensagens e contatos permanecem — eles são vinculados ao Projeto + contato, não à instância.

Como as conversas são chaveadas pelo contato (número), ao criar uma instância nova e parear o mesmo número, o histórico daquele número continua disponível pela API. Para enviar mensagens você precisa de uma instância conectada; para ler o histórico, não.

Mesmo depois de deletar a instância de origem, a conversa e suas mensagens continuam acessíveis — eles são chaveados pelo Projeto + contato, não pela instância. O instanceId que a conversa carrega é apenas a referência de por onde ela entrou; deletar a instância não apaga o histórico.

Persistido vs. efêmero

Cada Projeto tem um modo de histórico. O padrão — e o recomendado para a maioria das integrações — é persistido. Existe também um modo efêmero, usado em cenários específicos (por exemplo, números de atendimento onde só interessa o que está em aberto agora, sem manter um arquivo durável visível no Inbox).

ModoO que você vê no InboxOs dados são apagados?
Persistido (padrão)Todas as conversas do Projeto, sempre — independentemente de a instância estar conectada ou ter sido deletada.Não.
EfêmeroApenas as conversas cuja instância está CONNECTED no momento da consulta. Ao desconectar a instância, as conversas dela somem da listagem; ao reconectar, reaparecem.Não.

Efêmero NÃO apaga nada. As mensagens e conversas continuam gravadas no nosso banco em ambos os modos — o que muda é só o que aparece na listagem do Inbox. No modo efêmero, conversas de instâncias não-conectadas ficam ocultas enquanto a instância está fora; voltam a aparecer assim que ela fica CONNECTED de novo. Não é exclusão, é um filtro de visibilidade.

O estado CONNECTED é o da instância pareada e operacional — ver o ciclo de vida da instância. Qualquer outro estado (DISCONNECTED, CONNECTING, QR_PENDING, BANNED, ERROR) faz, no modo efêmero, as conversas daquela instância saírem da listagem até a reconexão.

O que muda na prática

  • Listagem de conversas (GET /v1/inbox/conversations) e contadores das abas (todas/abertas/pendentes/encerradas): no modo efêmero, ambos refletem as conversas de instâncias CONNECTED. No persistido, refletem tudo.
  • Detalhe e mensagens de uma conversa (GET /v1/inbox/conversations/{id} e /messages): respondem normalmente desde que você tenha o id da conversa — esses endpoints não aplicam o filtro de modo. Em outras palavras, mesmo no modo efêmero, se você guardou o id, ainda lê o histórico daquela conversa específica.
  • Webhooks e WebSocket: o modo de histórico não muda a entrega de eventos em tempo real. Mensagens recebidas continuam disparando message.received normalmente nos dois modos.

Qual é o padrão e como alterar

O modo padrão de todo Projeto novo é persistido. Em caso de erro transitório ao carregar a configuração do Projeto, a API assume persistido por segurança — para nunca esconder histórico por engano.

O modo de histórico é uma configuração do Projeto definida na criação dele. Não há endpoint público (key pzk_live_/pzk_test_) para o integrador alternar entre persistido e efêmero — não é um liga/desliga self-service como as configurações da instância. Para revisar ou mudar o modo do seu Projeto, fale com o suporte.

Para a esmagadora maioria das integrações, persistido (o padrão) é o que você quer: o Inbox vira um arquivo durável e você não precisa de banco próprio. Só considere o efêmero se o seu caso de uso for explicitamente "mostrar apenas o que está vivo agora".

Como ler o histórico

Use os endpoints de Inbox (autenticados pela mesma API key). Eles devolvem o histórico persistido, paginado, escopado ao seu Projeto e ao modo (teste/produção) da key. O Inbox lista apenas conversas 1-a-1 — grupos (@g.us), listas de transmissão (@broadcast) e canais (@newsletter) não aparecem.

Listar conversas

GET /v1/inbox/conversations devolve uma conversa por contato, ordenada por atividade (mais recente primeiro), 30 por página. Aceita ?status= (open, pending ou resolved; vazio = todas) e ?page= (padrão 1).

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

# filtrando por status e paginando
curl "https://api.pepinozap.com.br/v1/inbox/conversations?status=open&page=2" \
  -H "Authorization: Bearer $YOUR_API_KEY"

Resposta 200 OK. O bloco counts traz os totais por status de todo o Projeto (não só da página/filtro atual), úteis para os contadores das abas:

{
  "items": [
    {
      "id": "conv_abc123",
      "instanceId": "inst_xyz789",
      "protocol": "2026-000042",
      "contactId": "cont_def456",
      "contactName": "Maria Silva",
      "contactPhone": "5511999999999",
      "contactJid": "5511999999999@s.whatsapp.net",
      "status": "open",
      "assignedUserId": null,
      "unread": 2,
      "lastMessageAt": "2026-06-05T13:40:00Z",
      "lastMessageText": "Perfeito, obrigado!"
    }
  ],
  "page": 1,
  "counts": {
    "all": 37,
    "open": 12,
    "pending": 5,
    "resolved": 20
  }
}
CampoSignificado
idIdentificador da conversa. Guarde-o para ler as mensagens (abaixo).
instanceIdInstância pela qual a conversa entrou. Pode ser null em conversas que não estão vinculadas a uma instância. A conversa permanece acessível mesmo que a instância referenciada tenha sido deletada (o histórico não depende da instância).
protocolNúmero de protocolo do ticket no Projeto, no formato ano-sequência (null se não houver).
contactNameNome do contato. Pode ser null se ainda não foi resolvido/definido.
contactPhone / contactJidNúmero e JID do WhatsApp do contato.
statusopen, pending ou resolved.
assignedUserIdAtendente atribuído (null se não atribuída).
unreadMensagens não-lidas.
lastMessageAt / lastMessageTextCarimbo e prévia da última mensagem (ambos podem ser null em conversa sem mensagens).

Ler as mensagens de uma conversa

GET /v1/inbox/conversations/{id}/messages devolve as mensagens em ordem cronológica (mais antiga → mais nova), em páginas. Por padrão traz as limit mais recentes (limit padrão 50, máximo 200). Para o histórico completo (scrollback), pagine para trás com o cursor before.

# página mais recente (até 50 mensagens)
curl "https://api.pepinozap.com.br/v1/inbox/conversations/conv_abc123/messages" \
  -H "Authorization: Bearer $YOUR_API_KEY"

# página anterior (mais antigas): use o nextBefore da resposta anterior
curl "https://api.pepinozap.com.br/v1/inbox/conversations/conv_abc123/messages?before=msg_000123&limit=100" \
  -H "Authorization: Bearer $YOUR_API_KEY"

Resposta 200 OK:

{
  "items": [
    {
      "id": "msg_000122",
      "direction": "INBOUND",
      "type": "text",
      "text": "Bom dia, tudo bem?",
      "mediaUrl": null,
      "mediaMime": null,
      "status": "READ",
      "waMessageId": "3EB0XXXXXXXXXXXX",
      "quotedWaMessageId": null,
      "createdAt": "2026-06-05T13:38:00Z"
    },
    {
      "id": "msg_000123",
      "direction": "OUTBOUND",
      "type": "image",
      "text": "Segue a tabela",
      "mediaUrl": "https://s3.amazonaws.com/.../arquivo.jpg?X-Amz-Signature=...",
      "mediaMime": "image/jpeg",
      "status": "DELIVERED",
      "waMessageId": "3EB0YYYYYYYYYYYY",
      "quotedWaMessageId": null,
      "createdAt": "2026-06-05T13:40:00Z"
    }
  ],
  "hasMore": true,
  "nextBefore": "msg_000122"
}
CampoSignificado
directionINBOUND (recebida) ou OUTBOUND (enviada por você).
typetext, image, video, audio, document, sticker, location, contact, poll ou unknown.
textTexto/legenda da mensagem (null quando não houver).
mediaUrlURL pré-assinada (validade ~6h), só presente em mensagens de mídia. Releia o endpoint para obter uma nova.
mediaMimeContent-type da mídia (null se não for mídia).
statusEstado de entrega da mensagem. Para OUTBOUND evolui por SENT, DELIVERED e READ; as recebidas (INBOUND) chegam já como DELIVERED.
waMessageId / quotedWaMessageIdId da mensagem no WhatsApp e da mensagem citada (resposta), se houver.

Paginando o histórico completo: repita a chamada passando o nextBefore da resposta anterior em ?before= até hasMore virar false. Quando hasMore é false, nextBefore vem null — você chegou ao início da conversa.

Veja o contrato completo, com playground "Try it", em Lista as conversas e Lista as mensagens.

Mídia: re-hospedada por nós (por 2 anos). O binário fica no nosso storage por 2 anos; depois disso é removido (o texto da mensagem permanece). As mensagens de mídia trazem uma mediaUrl pré-assinada gerada na hora da leitura (validade ~6h). Se a URL expirar, basta reler o endpoint de mensagens para obter uma nova — dentro da janela de 2 anos você não precisa baixar nem armazenar o arquivo. (A mediaUrl que chega no webhook message.received é gerada com validade de 24h e serve para uso imediato.) Se precisar da mídia por mais de 2 anos, baixe e armazene do seu lado.

Erros possíveis (Inbox)

O envelope de erro é { "error": { "code", "message" } }, em que code é um rótulo string derivado do status HTTP (UNAUTHORIZED, FORBIDDEN, NOT_FOUND).

StatuscodeSignificado
401UNAUTHORIZEDAPI key ausente ou inválida — inclui usar a key no host do modo errado (ex.: pzk_live_ em sandbox.*).
403FORBIDDENProjeto inativo ou sem permissão.
404NOT_FOUNDConversa não encontrada (não existe, não é do seu Projeto, ou é de outro modo — teste/produção).

Tempo real, sem polling

Para reagir a mensagens novas e a mudanças de status na hora, use eventos em vez de ficar consultando a API:

  • Webhook — entrega garantida com retry (cadastrado por você via POST /v1/webhooks, self-service). Ver Webhooks.
  • WebSocket — self-service, conecta com a sua API key. Ver WebSocket.

O Inbox é a fonte do histórico; os eventos são para o tempo real. Juntos, cobrem todo o ciclo sem você manter banco próprio. O modo de histórico (persistido/efêmero) não altera a entrega desses eventos.

Migrando de uma stack própria

Se você hoje mantém infra própria para o WhatsApp (por exemplo, um banco de mensagens, um bucket de mídia e um canal de realtime), ao integrar o PEPINO ZAP você pode descontinuar essas três camadas:

Sua camada atualSubstituto no PEPINO ZAP
Banco de mensagens (ex.: DynamoDB/SQL)GET /v1/inbox/conversations + /messages
Storage de mídia (ex.: bucket S3)Nosso storage (mídia retida 2 anos) + mediaUrl na leitura
Realtime (ex.: WebSocket/pub-sub próprio)Webhook message.received/message.status ou WebSocket

Guardar uma cópia própria continua sendo opção sua (redundância, relatórios offline, retenção mais longa) — mas não é requisito. Para a maioria das integrações, ler do Inbox quando precisar é suficiente.

Próximos passos

On this page