Sincronizar histórico e contatos
Ao conectar um número em coexistência, você pode trazer para a Cloud API o histórico de conversas dos últimos 6 meses e a lista de contatos do aparelho.
Você tem 24 horas contadas do Embedded Signup, e uma única tentativa por tipo de sincronização. Passou o prazo ou errou, o cliente precisa refazer o onboarding inteiro. Não há recuperação.
Antes de disparar
1. O cliente autorizou o compartilhamento no celular. Durante o Embedded Signup, o aplicativo pede permissão para compartilhar histórico e contatos. Se ele recusou, nada será enviado — e o disparo não dá erro, simplesmente não chega webhook nenhum.
2. Os eventos estão assinados. Sem isso, os dados são enviados e descartados.
curl -X PATCH https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "events": ["messages", "history", "smb_app_state_sync"] }'
3. Seu servidor aguenta o volume. Um único webhook pode carregar milhares de
mensagens. Responda 2xx na hora e processe em background.
Disparando
curl -X POST https://api.joinotify.com/v1/106540352242922/smb_app_data \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "sync_type": "history" }'
curl -X POST https://api.joinotify.com/v1/106540352242922/smb_app_data \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "sync_type": "smb_app_state_sync" }'
A resposta apenas confirma o agendamento. Os dados chegam depois, de forma assíncrona.
Como o histórico chega
Em múltiplos webhooks, divididos em fases e blocos.
webhook (1 POST)
└── value.history[]
└── threads[] ← 1 thread = 1 conversa com 1 contato
└── messages[] ← mensagens daquela conversa
Fases
phase | Período nominal |
|---|---|
0 | dia 0 → dia 1 |
1 | dia 1 → dia 90 |
2 | dia 90 → dia 180 |
Na prática, mensagens de qualquer data aparecem em qualquer fase. Não use a fase para
inferir período. Ordene tudo por timestamp.
Blocos e progresso
Cada fase pode vir em vários blocos (chunk_order), não necessariamente em ordem.
{ "phase": 1, "chunk_order": 3, "progress": 60 }
progress vai de 0 a 100. progress: 100 na última fase significa histórico
completo — é o sinal de que você pode encerrar o processamento.
Deduplicação — obrigatória
O mesmo thread.id aparece em vários webhooks, e a mesma mensagem pode vir repetida.
- Junte todas as mensagens com o mesmo
thread.idde todos os webhooks. - Deduplique por
wamid(message.id). - Ordene por
timestamp— Unix em segundos, 10 dígitos.
const conversas = new Map();
function processarHistorico(value) {
for (const bloco of value.history ?? []) {
for (const thread of bloco.threads ?? []) {
const atual = conversas.get(thread.id) ?? new Map();
for (const msg of thread.messages ?? []) {
atual.set(msg.id, msg); // dedup por wamid
}
conversas.set(thread.id, atual);
}
}
}
function finalizar(threadId) {
return [...conversas.get(threadId).values()].sort((a, b) => Number(a.timestamp) - Number(b.timestamp));
}
Direção da mensagem
history_context.from_me: true = enviada pela empresa, false ou ausente = recebida.
Mídias no histórico
Mídias não vêm dentro das threads. Elas aparecem como type: "media_placeholder",
sem conteúdo:
{ "id": "wamid.HBgN...", "type": "media_placeholder", "timestamp": "1785312000" }
O arquivo real chega em webhooks separados (value.messages ou
value.message_echoes), casados pelo wamid.
Arquivos mais antigos que isso não têm conteúdo disponível — o placeholder é tudo que você recebe. É limitação da Meta, não do Joinotify.
Como os contatos chegam
Geralmente em um único webhook, no array state_sync. Contas grandes podem gerar vários,
sem ordenação garantida.
{
"field": "smb_app_state_sync",
"value": {
"state_sync": [
{
"type": "contact",
"action": "add",
"contact": { "phone_number": "5541999999999", "full_name": "Maria Silva" },
"metadata": { "timestamp": "1785312000000" }
}
]
}
}
action: "add"→ adicionar ou atualizaraction: "remove"→ remover
Contatos usam milissegundos (13 dígitos); histórico usa segundos (10 dígitos). Misturar os dois gera datas em 1970 ou no ano 57000.
Depois da sincronização inicial
Alterações futuras nos contatos continuam chegando automaticamente por
smb_app_state_sync — sem precisar chamar o endpoint de novo. O disparo único vale
apenas para a carga inicial.
Checklist
- Cliente autorizou o compartilhamento no celular
-
historyesmb_app_state_syncassinados no endpoint de webhook - Endpoint responde
2xxem segundos e processa em background - Dedup por
wamidimplementado - Ordenação por
timestamp, não por ordem de chegada -
media_placeholdertratado, com reconciliação posterior pelowamid - Timestamps de contato lidos em milissegundos
- Disparo dentro das 24 horas