WhatsApp tem taxa de abertura acima de 90% — contra ~20% do e-mail. Para atendimento ao cliente, isso significa menos tickets ignorados, resolução mais rápida e clientes mais satisfeitos. Neste guia você aprende a montar um sistema de atendimento profissional usando a ZAP API.
Arquitetura de um sistema de atendimento WhatsApp
Um sistema de atendimento eficiente tem três camadas:
- Recepção — webhook captura todas as mensagens recebidas
- Triagem automática — chatbot responde dúvidas simples via IA
- Fila humana — casos complexos são direcionados para atendentes
// Arquitetura simplificada
Cliente → WhatsApp → ZAP API Webhook → [Triagem IA]
↓ ↓
Resposta Fila Humana
automática → Atendente
→ Notifica via Slack/Email
Passo 1: Receber e categorizar mensagens
import express from 'express';
import axios from 'axios';
const app = express();
app.use(express.json());
const ZAP_TOKEN = process.env.ZAP_TOKEN;
const ZAP_INSTANCE = process.env.ZAP_INSTANCE;
// Categorias de atendimento
type Category = 'faq' | 'pedido' | 'reclamacao' | 'humano';
function categorize(body: string): Category {
const text = body.toLowerCase();
if (/status|rastr|entrega|pedido/.test(text)) return 'pedido';
if (/reclamar|insatisf|problema|errado/.test(text)) return 'reclamacao';
if (/falar|atendente|humano|pessoa/.test(text)) return 'humano';
return 'faq';
}
app.post('/webhook', async (req, res) => {
res.sendStatus(200);
const { event, data } = req.body;
if (event !== 'message.received') return;
const category = categorize(data.body);
await routeMessage(data, category);
});
interface ChatMessage {
phone: string;
name?: string;
body?: string;
messageId?: string;
sessionId?: string;
metadata?: Record;
}
async function routeMessage(data: ChatMessage, category: Category) {
switch (category) {
case 'faq':
await handleFaq(data);
break;
case 'pedido':
await handleOrderStatus(data);
break;
case 'reclamacao':
await escalateToHuman(data, '🔴 Reclamação');
break;
case 'humano':
await escalateToHuman(data, '👤 Solicitou atendimento humano');
break;
}
}
Passo 2: Resposta automática com IA para FAQ
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const SYSTEM_PROMPT = `
Você é o assistente de suporte da Minha Empresa.
Responda em português, seja breve (máx 3 frases) e preciso.
Se não souber, diga "Vou verificar com nossa equipe e retorno em breve."
Informações do produto:
- Horário de atendimento: seg-sex 9h-18h
- Prazo de entrega: 3-5 dias úteis
- Política de troca: 30 dias após recebimento
- Suporte: [email protected]
`;
async function handleFaq(data: ChatMessage) {
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: data.body }
]
});
const reply = completion.choices[0].message.content ?? 'Desculpe, não entendi. Pode reformular?';
await sendMessage(data.phone, reply);
}
async function sendMessage(phone: string, text: string) {
await axios.post(
`https://zap-api.tech/v1/instances/${ZAP_INSTANCE}/send`,
{ phone, type: 'text', body: text },
{ headers: { Authorization: `Bearer ${ZAP_TOKEN}` } }
);
}
Passo 3: Fila de atendimento humano
// Estrutura da fila em memória (use Redis em produção)
interface Ticket {
id: string;
phone: string;
name: string;
reason: string;
createdAt: Date;
status: 'waiting' | 'in_progress' | 'resolved';
agentId?: string;
}
const ticketQueue: Ticket[] = [];
async function escalateToHuman(data: ChatMessage, reason: string) {
const ticket: Ticket = {
id: `TK-${Date.now()}`,
phone: data.phone,
name: data.name,
reason,
createdAt: new Date(),
status: 'waiting'
};
ticketQueue.push(ticket);
// Confirma para o cliente
await sendMessage(data.phone,
`✅ Entendido! Você está na fila de atendimento (posição ${ticketQueue.length}).\n` +
`Um atendente responderá em breve. Horário: seg-sex 9h-18h.`
);
// Notifica a equipe (Slack, email, etc.)
await notifyTeam(ticket);
}
// API para atendentes visualizarem a fila
app.get('/tickets', (req, res) => {
res.json(ticketQueue.filter(t => t.status === 'waiting'));
});
// Atendente assume um ticket
app.post('/tickets/:id/claim', async (req, res) => {
const ticket = ticketQueue.find(t => t.id === req.params.id);
if (!ticket) return res.status(404).json({ error: 'Ticket não encontrado' });
ticket.status = 'in_progress';
ticket.agentId = req.body.agentId;
await sendMessage(ticket.phone,
`👋 Olá ${ticket.name}! Sou ${req.body.agentName} e vou te ajudar agora.`
);
res.json({ ok: true, ticket });
});
Passo 4: CSAT automático após resolução
Avalie a satisfação do cliente automaticamente ao fechar um ticket:
// Fecha ticket e dispara pesquisa de satisfação
app.post('/tickets/:id/resolve', async (req, res) => {
const ticket = ticketQueue.find(t => t.id === req.params.id);
if (!ticket) return res.status(404).json({ error: 'Not found' });
ticket.status = 'resolved';
// Aguarda 2 min e envia CSAT
setTimeout(async () => {
await sendMessage(ticket.phone,
`Seu atendimento foi resolvido! 🎉\n\n` +
`Como você avalia nosso suporte de 1 a 5?\n` +
`1 = Péssimo | 5 = Excelente`
);
}, 2 * 60 * 1000);
res.json({ ok: true });
});
Métricas essenciais para monitorar
| Métrica | Meta | Como calcular |
|---|---|---|
| Tempo médio de resposta (TMA) | < 5 min | Δt entre mensagem e 1ª resposta |
| Tempo médio de resolução (TMR) | < 30 min | Δt entre abertura e fechamento |
| Taxa de resolução automática | > 60% | Tickets resolvidos pela IA / total |
| CSAT | > 4.2/5 | Média das avaliações recebidas |
| Taxa de abandono | < 10% | Clientes que não responderam na fila |
Horário de atendimento e fora de expediente
function isBusinessHours(): boolean {
const now = new Date();
const hour = now.getHours();
const day = now.getDay(); // 0 = domingo, 6 = sábado
return day >= 1 && day <= 5 && hour >= 9 && hour < 18;
}
app.post('/webhook', async (req, res) => {
res.sendStatus(200);
const { event, data } = req.body;
if (event !== 'message.received') return;
if (!isBusinessHours()) {
await sendMessage(data.phone,
`Olá! 👋 Nosso horário de atendimento é seg-sex das 9h às 18h.\n` +
`Sua mensagem foi registrada e responderemos no próximo dia útil.`
);
return;
}
// ... lógica normal de atendimento
});
FAQ
Quantos atendentes podem usar o sistema ao mesmo tempo?
Não há limite por parte da ZAP API. O limite é da sua implementação da fila. Com Redis e workers adequados, você pode escalar para dezenas de atendentes simultâneos.
Como evitar que o cliente receba respostas duplicadas (IA + humano)?
Implemente um estado por sessão: quando um ticket está in_progress, desative as respostas automáticas para aquele número até que seja resolved.
É possível integrar com ferramentas de CRM como HubSpot ou Pipedrive?
Sim. Use as APIs oficiais desses CRMs para criar/atualizar contatos e negócios a partir dos eventos do webhook WhatsApp. A integração é feita no seu backend.
Qual o custo para começar?
A ZAP API oferece 7 dias de trial grátis sem cartão. Após o trial, a partir de R$29/mês por instância WhatsApp — sem cobrança por mensagem.