Skip to content

Atendimento humano (inbox web)

Caso de uso classico: voce quer um inbox web pra equipe ler/responder WhatsApp, com historico, atribuicao de conversa por agente, etc. Esta receita mostra a arquitetura e os pontos de integracao com o WPP Hub.

Arquitetura sugerida

[Cliente WhatsApp]
│ msg in
[Chip do operador]
│ Baileys → hub
[WPP Hub] ── webhook message.received ──▶ [Seu backend]
▲ │
│ POST /v1/messages ▼
│ [DB conversas]
└────────────────────────────────────── [Inbox web]
│ (operador digita resposta)
│ POST /api/send

Fluxo de inbound (mensagem chegando)

  1. Cliente manda WhatsApp pro chip
  2. Hub persiste em messages (direction=‘in’)
  3. Hub dispara message.received pro seu webhook
  4. Voce valida HMAC, processa:
    • Cria/atualiza conversation no seu DB (chave: chat_jid)
    • Cria message interno
    • Notifica agente atribuido (push, websocket, etc)
  5. Agente abre conversa no inbox web
app.post('/webhook', async (req, res) => {
// ... validacao HMAC ...
res.sendStatus(200)
const m = payload.data
if (payload.event === 'message.received' && m.direction === 'in') {
const conv = await upsertConversation({
chatJid: m.chat.jid,
chipId: m.chip_id,
contactPhone: m.sender.phone_number,
contactName: m.sender.name || m.sender.verified_business_name,
isGroup: m.chat.is_group,
groupName: m.chat.name,
})
await db.message.create({
conversationId: conv.id,
direction: 'in',
type: m.type,
content: m.content,
mediaId: m.content.media_id || null,
waMessageId: m.wa_message_id,
hubMessageId: m.message_id,
timestamp: new Date(m.timestamp),
})
// Notifica agente atribuido (websocket, etc)
if (conv.assignedAgentId) {
io.to(`agent:${conv.assignedAgentId}`).emit('new-message', {
conversationId: conv.id,
...
})
}
}
})

Fluxo de outbound (agente responde)

// API do seu backend que o inbox web chama
app.post('/api/conversations/:id/reply', async (req, res) => {
const { text } = req.body
const conv = await db.conversation.findById(req.params.id)
const agentId = req.user.id
// 1. Cria registro local (otimista — UI mostra "enviando")
const localMsg = await db.message.create({
conversationId: conv.id,
direction: 'out',
type: 'text',
content: { text },
sentBy: agentId,
status: 'queued',
})
// 2. Manda pro hub
try {
const hubRes = await fetch(`${HUB}/v1/messages`, {
method: 'POST',
headers: { Authorization: `Bearer ${APP_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
chip_id: conv.chipId,
to: conv.contactPhone, // ou conv.chatJid
type: 'text',
content: { text },
idempotency_key: `local-${localMsg.id}`, // dedup com seu ID
}),
})
const { id: hubMessageId } = await hubRes.json()
await db.message.update(localMsg.id, { hubMessageId })
res.json(localMsg)
} catch (e) {
await db.message.update(localMsg.id, { status: 'failed', error: e.message })
res.status(500).json({ error: e.message })
}
})

Acompanhar status de envio

Hub manda message.status quando muda (sent → delivered → read):

if (payload.event === 'message.status') {
const { message_id, status } = payload.data
await db.message.updateMany(
{ hubMessageId: message_id },
{ status, [`${status}At`]: new Date() },
)
// notifica UI via websocket
}

Recursos extras pro inbox

Foto + nome do contato

Use endpoint de contatos pra buscar:

async function getContactInfo(chipId, jid) {
const res = await fetch(
`${HUB}/v1/chips/${chipId}/contacts/${encodeURIComponent(jid)}`,
{ headers: { Authorization: `Bearer ${ADMIN_KEY}` } },
)
if (res.status === 404) return null
return res.json() // { name, notify, phone_number, photo_url, ... }
}

photo_url ja vem como path relativo. Concatene com base URL e use <img>.

Atribuir conversa a agente

E logica do seu DB. WPP Hub nao gerencia agentes (intencional — escopo).

Tags / classificacao automatica

Idem — logica do seu app. Exemplo: keyword spotting no content.text, atribui tag, roteia pra fila.

Templates de resposta

Idem.

Transcricao de audio

Liga via app_chip_access.transcribe_audio_mode='all' quando crias o vinculo. Hub transcreve via Whisper, dispara message.transcribed separado. Voce salva o texto na conversation.

Ver Receita transcricao.

Cuidados

  1. Agente assumindo conversa — quando 2 agentes abrem ao mesmo tempo, lock no DB pra evitar duplicacao
  2. Self-chat — operador manda mensagem do proprio celular pareado: vem com direction='out' mas from_me=true. Hub so dispara webhook se app_chip_access.receive_self_chat=true. Util pra log de mensagens manuais
  3. Cliente troca de contato — uma conversa pode ter contato com phone_number E lid mudando dependendo da mensagem (privacidade WA). Use chat_jid como chave de conversa, nao phone_number
  4. Mensagens de grupochat.is_group=true, sender.jid (membro) != from (grupo). Pra grupos, conversa e o grupo, mensagens sao agregadas por sender.jid