Pular para o conteúdo principal

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.

Leia isto antes de disparar

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​

Histórico de conversas
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" }'
Lista de contatos
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​

phasePeríodo nominal
0dia 0 → dia 1
1dia 1 → dia 90
2dia 90 → dia 180
As fases se sobrepõem

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.

  1. Junte todas as mensagens com o mesmo thread.id de todos os webhooks.
  2. Deduplique por wamid (message.id).
  3. 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.

Só ~14 dias de mídia

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 atualizar
  • action: "remove" → remover
Unidade de tempo diferente

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
  • history e smb_app_state_sync assinados no endpoint de webhook
  • Endpoint responde 2xx em segundos e processa em background
  • Dedup por wamid implementado
  • Ordenação por timestamp, não por ordem de chegada
  • media_placeholder tratado, com reconciliação posterior pelo wamid
  • Timestamps de contato lidos em milissegundos
  • Disparo dentro das 24 horas