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/sendFluxo de inbound (mensagem chegando)
- Cliente manda WhatsApp pro chip
- Hub persiste em
messages(direction=‘in’) - Hub dispara
message.receivedpro seu webhook - Voce valida HMAC, processa:
- Cria/atualiza
conversationno seu DB (chave:chat_jid) - Cria
messageinterno - Notifica agente atribuido (push, websocket, etc)
- Cria/atualiza
- 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 chamaapp.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
- Agente assumindo conversa — quando 2 agentes abrem ao mesmo tempo, lock no DB pra evitar duplicacao
- Self-chat — operador manda mensagem do proprio celular pareado: vem com
direction='out'masfrom_me=true. Hub so dispara webhook seapp_chip_access.receive_self_chat=true. Util pra log de mensagens manuais - Cliente troca de contato — uma conversa pode ter contato com
phone_numberElidmudando dependendo da mensagem (privacidade WA). Usechat_jidcomo chave de conversa, naophone_number - Mensagens de grupo —
chat.is_group=true,sender.jid(membro) !=from(grupo). Pra grupos, conversa e o grupo, mensagens sao agregadas porsender.jid