Comparativo técnico: webhooks vs. polling em sistemas de agendamento

Quem integra um sistema de agendamento a um CRM, a um ERP ou a um painel próprio precisa decidir cedo como os dois lados vão se manter sincronizados. Existem duas respostas, e a escolha entre elas define latência, custo de infraestrutura e quantos pontos de falha o integrador vai ter que monitorar depois.
No polling, o seu sistema pergunta de tempos em tempos: um GET /api/v3/appointments/ a cada cinco minutos, por exemplo. No webhook, a plataforma avisa — uma requisição HTTP chega ao seu endpoint no instante em que o agendamento é criado ou alterado.
A diferença parece só de direção, mas as consequências não são simétricas.
O custo escondido do polling é a razão entre consultas e eventos
Polling é mais simples de implementar. Não exige endpoint público, não exige tratar retentativa de entrega, e o integrador controla a frequência. Para quem está começando ou precisa de algo funcionando no mesmo dia, é o caminho mais curto.
O problema aparece na aritmética. Uma consulta a cada cinco minutos são 288 requisições por dia por conta integrada. Se a agenda recebe 20 agendamentos diários, 268 dessas requisições não trouxeram novidade alguma — mais de 90% de tráfego descartado, pago pelos dois lados. Quando a integração cresce para dezenas de contas, o desperdício cresce junto, e a resposta natural é aumentar o intervalo, o que piora a latência.
E a latência do polling é sempre pior do que parece. Com ciclo de cinco minutos, o atraso médio é de dois minutos e meio, mas o pior caso é cinco. Para relatório diário isso é irrelevante. Para avisar um profissional de que a próxima consulta acabou de ser cancelada, cinco minutos é a diferença entre reocupar o horário e perdê-lo.
Webhook troca simplicidade por infraestrutura
O webhook resolve os dois problemas de uma vez: só há tráfego quando há evento, e a notificação chega em segundos. Em troca, transfere responsabilidade para o lado de quem integra.
Você precisa de um endpoint acessível pela internet, disponível quando o evento acontecer. Precisa validar a origem da requisição, porque um endpoint público que aceita qualquer POST é um convite a dados forjados — token no header ou assinatura HMAC no corpo resolvem. E precisa decidir o que fazer quando seu sistema estiver fora do ar no momento da entrega.
Esse último ponto é o que costuma ser subestimado. Polling é resiliente por natureza: se o seu processo caiu, o próximo ciclo busca tudo o que ficou para trás. Webhook não tem essa propriedade — evento perdido durante uma janela de indisponibilidade não volta sozinho. Na prática, integrações sérias com webhook mantêm um polling de baixa frequência como rede de segurança, justamente para reconciliar o que escapou.
| Critério | Polling | Webhooks |
|---|---|---|
| Latência | Metade do intervalo, na média | Segundos |
| Uso de recursos | Alto — requisições mesmo sem novidade | Baixo — só quando há evento |
| Complexidade | Baixa | Média: endpoint, autenticação, retentativa |
| Recuperação após queda | Automática no próximo ciclo | Exige reconciliação |
| Onde faz sentido | Relatórios, carga histórica, batch noturno | Cancelamento, confirmação, notificação ao cliente |
Implementando polling
Um agendador dispara a consulta em intervalo fixo. O exemplo abaixo usa schedule e requests, mas a lógica é a mesma em qualquer stack — inclusive em um cron job do sistema, que costuma ser mais robusto que manter um processo Python vivo indefinidamente.
import requests
import schedule
import time
def fetch_appointments():
url = "https://eagenda.com.br/api/v3/appointments/"
headers = {
"Authorization": "Bearer seu-token-aqui",
"Content-Type": "application/json"
}
params = {
"account_slug": "minha-conta",
"status": "CONFIRMED",
"start_date": "2025-06-01T00:00:00Z",
"end_date": "2025-06-30T23:59:59Z",
"page_size": 10
}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
print("Agendamentos:", response.json()["results"])
else:
print("Erro:", response.status_code)
schedule.every(5).minutes.do(fetch_appointments)
while True:
schedule.run_pending()
time.sleep(60)
Duas melhorias valem o esforço desde o início: filtrar por data de modificação em vez de reprocessar a janela inteira a cada ciclo, e tratar a paginação — page_size: 10 funciona no teste e falha silenciosamente no primeiro dia de movimento real.
Implementando webhook
Do lado receptor, o endpoint precisa responder rápido e validar antes de processar.
const express = require('express');
const app = express();
app.use(express.json());
app.post('/eagenda-webhook', (req, res) => {
// Valide o token antes de qualquer processamento
if (req.headers['x-webhook-token'] !== process.env.WEBHOOK_TOKEN) {
return res.status(401).send('Unauthorized');
}
// Responda primeiro, processe depois: entrega lenta vira retentativa
res.status(200).send('OK');
enfileirarEvento(req.body);
});
app.listen(3000, () => console.log('Webhook rodando na porta 3000'));
Responder 200 antes de processar não é detalhe de estilo. Se o seu handler leva oito segundos para gravar no banco e a plataforma considera a entrega falha após cinco, você recebe o mesmo evento repetidamente. Enfileirar e confirmar rápido evita esse laço — e obriga o processamento a ser idempotente, já que reentrega é normal em qualquer sistema de webhook.
O payload entregue traz o agendamento completo, o que dispensa uma segunda chamada à API para buscar detalhes:
{
"appointment_key": "191e1237-0240-4000-8ac1-60bcc5ae8701",
"status": "ATTENDED",
"calendar": {
"calendar_key": "191e1237-0240-4000-85e4-6430bf42cf01",
"calendar_name": "Agenda Principal"
},
"service_list": [
{
"service_key": "191e1237-0240-4000-81be-0e5d361a8501",
"service_name": "Consulta Médica"
}
],
"tag_list": [
{
"tag_key": "191e1237-0240-4000-8921-719af633b401",
"label": "Urgente"
}
],
"attendees": [
{
"person_key": "191e1237-0240-4000-8ed4-9fac97c84d01",
"name": "João Silva",
"email": "joao.silva@example.com",
"phone": "+5511942529100"
}
],
"owner_user": {
"email": "admin@example.com",
"full_name": "Dr. Maria Souza"
},
"start": {
"dateTime": "2025-06-01T10:00:00Z",
"timeZone": "UTC"
},
"end": {
"dateTime": "2025-06-01T10:30:00Z",
"timeZone": "UTC"
},
"created_at": {
"dateTime": "2025-06-01T09:58:00Z",
"timeZone": "UTC"
},
"location": "Clínica Central",
"conference_data": {
"provider": "zoom",
"url": "http://example.com",
"label": "Reunião Online",
"pin": "123456",
"access_code": "7890",
"meeting_code": "123456789",
"passcode": "abc123"
},
"event": "appointment.created"
}
Repare no campo event: o mesmo endpoint recebe criação, atualização e cancelamento, então o roteamento por tipo de evento precisa existir desde a primeira versão. Tratar tudo como criação funciona até o primeiro cancelamento chegar.
Como decidir
A pergunta útil não é qual tecnologia é melhor, e sim quanto atraso o seu caso tolera. Se a resposta for “alguns minutos não fazem diferença” — sincronização de relatório, carga histórica, conciliação noturna —, polling entrega o resultado com menos peça para manter, e não há mérito em complicar.
Se a resposta envolver alguém esperando do outro lado — cliente que precisa saber que a consulta foi confirmada, profissional que quer reocupar um cancelamento, CRM que dispara fluxo a partir do agendamento —, o webhook é o caminho, com a ressalva de que ele exige tratar autenticação, idempotência e reconciliação. Pular essas três coisas produz uma integração que funciona na demonstração e falha em produção.