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.
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):
| Dado | Onde fica | Você precisa guardar? |
|---|---|---|
| Conversas (uma por contato) | Nosso Postgres | Não |
| Mensagens (enviadas e recebidas) | Nosso Postgres | Não |
| Contatos | Nosso Postgres | Nã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ção | O que acontece |
|---|---|
| Desconectar a instância | Nada é apagado. A instância e a sessão ficam; reconecta sozinha. Histórico intacto. |
| Deletar a instância | Remove 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).
| Modo | O que você vê no Inbox | Os 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êmero | Apenas 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 só as conversas de instânciasCONNECTED. No persistido, refletem tudo. - Detalhe e mensagens de uma conversa (
GET /v1/inbox/conversations/{id}e/messages): respondem normalmente desde que você tenha oidda conversa — esses endpoints não aplicam o filtro de modo. Em outras palavras, mesmo no modo efêmero, se você guardou oid, 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.receivednormalmente 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
}
}| Campo | Significado |
|---|---|
id | Identificador da conversa. Guarde-o para ler as mensagens (abaixo). |
instanceId | Instâ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). |
protocol | Número de protocolo do ticket no Projeto, no formato ano-sequência (null se não houver). |
contactName | Nome do contato. Pode ser null se ainda não foi resolvido/definido. |
contactPhone / contactJid | Número e JID do WhatsApp do contato. |
status | open, pending ou resolved. |
assignedUserId | Atendente atribuído (null se não atribuída). |
unread | Mensagens não-lidas. |
lastMessageAt / lastMessageText | Carimbo 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"
}| Campo | Significado |
|---|---|
direction | INBOUND (recebida) ou OUTBOUND (enviada por você). |
type | text, image, video, audio, document, sticker, location, contact, poll ou unknown. |
text | Texto/legenda da mensagem (null quando não houver). |
mediaUrl | URL pré-assinada (validade ~6h), só presente em mensagens de mídia. Releia o endpoint para obter uma nova. |
mediaMime | Content-type da mídia (null se não for mídia). |
status | Estado de entrega da mensagem. Para OUTBOUND evolui por SENT, DELIVERED e READ; as recebidas (INBOUND) chegam já como DELIVERED. |
waMessageId / quotedWaMessageId | Id 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).
| Status | code | Significado |
|---|---|---|
401 | UNAUTHORIZED | API key ausente ou inválida — inclui usar a key no host do modo errado (ex.: pzk_live_ em sandbox.*). |
403 | FORBIDDEN | Projeto inativo ou sem permissão. |
404 | NOT_FOUND | Conversa 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 atual | Substituto 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
- Sua primeira instância — criar, parear e configurar a instância.
- Autenticação — API key, modos teste/produção e o fallback
?token=. - Lista as conversas — referência completa do Inbox.
- Webhooks — receba eventos de mensagens e de status da instância em tempo real.
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.
Visão geral
Convenções da API REST do PEPINO ZAP — base URLs e modos teste/produção, autenticação por API key, formato do campo to (E.164/JID), envelope de erro, envio assíncrono e índice de endpoints.