TL;DR: Polling verifica atualizações periodicamente (simples, mas ineficiente). Webhooks enviam atualizações em tempo real (eficiente, mas complexo). Use polling para verificações infrequentes, webhooks para atualizações em tempo real. O Modern PetstoreAPI suporta ambos os padrões com entrega confiável de webhooks.
Entendendo a Diferença
Polling: O cliente pergunta “Há alguma atualização?” repetidamente. Webhooks: O servidor diz “Aqui está uma atualização!” quando algo acontece.
Analogia:
- Polling = Verificar sua caixa de correio a cada hora
- Webhooks = O carteiro toca a campainha quando a correspondência chega
Polling: Como Funciona
O cliente faz requisições periódicas para verificar alterações.
// Poll a cada 30 segundos
setInterval(async () => {
const response = await fetch('https://petstoreapi.com/api/v1/orders/123');
const order = await response.json();
if (order.status === 'completed') {
console.log('Order completed!', order);
clearInterval(pollInterval);
}
}, 30000);
Padrões de Polling:
Polling simples:
GET /api/v1/orders/123
# Retorna o estado atual do pedido
Polling condicional (ETag):
GET /api/v1/orders/123
If-None-Match: "abc123"
# Retorna 304 Not Modified se inalterado
# Retorna 200 com novos dados se alterado
Polling baseado em "desde":
GET /api/v1/orders/123/events?since=1710331200
# Retorna eventos desde o timestamp
Webhooks: Como Funcionam
O servidor envia um HTTP POST para o seu endpoint quando eventos ocorrem.
Fluxo de Configuração:
// 1. Registrar endpoint do webhook
POST /api/v1/webhooks
{
"url": "https://myapp.com/webhooks/petstore",
"events": ["order.created", "order.completed"],
"secret": "whsec_abc123"
}
// 2. O servidor envia o webhook quando o evento ocorre
POST https://myapp.com/webhooks/petstore
{
"id": "evt_123",
"type": "order.completed",
"created": 1710331200,
"data": {
"orderId": "123",
"status": "completed",
"completedAt": "2024-01-01T12:00:00Z"
}
}
// 3. Verificar e processar o webhook
// Responde com 200 OK
Quando Usar Polling
Bom para:
- Verificações infrequentes (uma vez por hora)
- Pequeno número de recursos
- Implementações simples
- Quando você controla o cliente
- Testes e depuração
Exemplos:
- Verificar status de relatórios diários
- Sincronizar contatos a cada poucos minutos
- Monitorar a saúde do servidor
- Verificar status de pagamento (infrequente)
Polling é adequado quando:
- As atualizações são raras
- Um pequeno atraso é aceitável
- Você deseja uma implementação simples
- O recurso é pequeno
Quando Usar Webhooks
Bom para:
- Atualizações em tempo real
- Muitos recursos para monitorar
- Eventos sensíveis ao tempo
- Integrações de terceiros
- Atualizações de alta frequência
Exemplos:
- Confirmações de pagamento
- Mensagens de chat
- Alertas de preço de ações
- Mudanças de status de pedidos
- Notificações de CI/CD
Webhooks são melhores quando:
- As atualizações precisam ser imediatas
- Polling seria ineficiente
- Muitos clientes monitorando o mesmo recurso
- Você deseja reduzir a carga do servidor
Tabela Comparativa
| Fator | Polling | Webhooks |
|---|---|---|
| Latência | Até o intervalo de polling | Tempo real |
| Carga do servidor | Alta (muitas requisições vazias) | Baixa (apenas eventos reais) |
| Complexidade | Simples | Complexo |
| Confiabilidade | Alta (o cliente controla a retentativa) | Média (precisa de lógica de retentativa) |
| Configuração | Nenhum | Registro de endpoint |
| Problemas de firewall | Nenhum (apenas saída) | Pode precisar de whitelisting |
| Custo | Maior (mais requisições) | Menor (menos requisições) |
| Melhor para | Verificações infrequentes | Atualizações em tempo real |
Implementando Polling
Polling Básico
async function pollOrderStatus(orderId, callback) {
let lastStatus = null;
const poll = async () => {
try {
const response = await fetch(`https://petstoreapi.com/api/v1/orders/${orderId}`);
const order = await response.json();
// Only callback if status changed
if (order.status !== lastStatus) {
lastStatus = order.status;
callback(order);
}
// Stop polling if terminal state
if (['completed', 'cancelled'].includes(order.status)) {
return;
}
// Continue polling
setTimeout(poll, 5000);
} catch (error) {
console.error('Polling error:', error);
setTimeout(poll, 30000); // Back off on error
}
};
poll();
}
// Uso
pollOrderStatus('order-123', (order) => {
console.log(`Order status: ${order.status}`);
});
Polling Inteligente (Exponential Backoff)
async function smartPoll(url, callback, options = {}) {
const {
maxRetries = 10,
initialInterval = 1000,
maxInterval = 60000,
stopCondition = () => false
} = options;
let retries = 0;
let interval = initialInterval;
let lastData = null;
const poll = async () => {
try {
const response = await fetch(url);
const data = await response.json();
// Callback se os dados mudaram
if (JSON.stringify(data) !== JSON.stringify(lastData)) {
lastData = data;
callback(data);
}
// Para se condição for atendida
if (stopCondition(data)) {
return;
}
// Resetar intervalo em sucesso
interval = initialInterval;
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw new Error('Max retries exceeded');
}
}
// Agendar próximo poll com backoff exponencial
setTimeout(poll, interval);
interval = Math.min(interval * 2, maxInterval);
};
poll();
}
// Uso: Poll até pedido ser completado
smartPoll('https://petstoreapi.com/api/v1/orders/123',
(order) => console.log('Order:', order),
{
stopCondition: (order) => ['completed', 'cancelled'].includes(order.status),
initialInterval: 2000,
maxInterval: 30000
}
);
Polling com ETag
async function pollWithEtag(url, callback) {
let etag = null;
const poll = async () => {
const headers = {};
if (etag) {
headers['If-None-Match'] = etag;
}
const response = await fetch(url, { headers });
if (response.status === 304) {
// Não modificado, continuar polling
setTimeout(poll, 30000);
return;
}
const data = await response.json();
etag = response.headers.get('etag');
callback(data);
setTimeout(poll, 30000);
};
poll();
}
Implementando Webhooks
Registrando Webhooks
// Registrar endpoint do webhook
async function registerWebhook(url, events) {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
url,
events,
secret: generateSecret()
})
});
return response.json();
}
function generateSecret() {
return 'whsec_' + crypto.randomBytes(32).toString('hex');
}
Recebendo Webhooks
const express = require('express');
const crypto = require('crypto');
const app = express();
// Raw body parser para verificação de assinatura
app.use('/webhooks', express.raw({ type: 'application/json' }));
app.post('/webhooks/petstore', async (req, res) => {
const signature = req.headers['x-petstore-signature'];
const body = req.body;
// Verifica assinatura
const isValid = verifySignature(body, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(body.toString());
// Processa evento
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
case 'order.completed':
await handleOrderCompleted(event.data);
break;
case 'order.cancelled':
await handleOrderCancelled(event.data);
break;
}
// Confirma recebimento
res.status(200).json({ received: true });
});
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Testando Webhooks Localmente
# Use ngrok para expor endpoint local
ngrok http 3000
# Registre a URL do ngrok como endpoint de webhook
curl -X POST https://petstoreapi.com/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/petstore",
"events": ["order.created", "order.completed"]
}'
Entrega Confiável de Webhooks
Webhooks podem falhar. Implemente lógica de retentativa.
Lado do Remetente (Servidor)
// Enfileira webhooks para entrega
const webhookQueue = [];
async function sendWebhook(event) {
const webhooks = await db.webhooks.findMany({
where: { events: { contains: event.type } }
});
for (const webhook of webhooks) {
webhookQueue.push({
webhook,
event,
attempts: 0,
nextAttempt: Date.now()
});
}
processQueue();
}
async function processQueue() {
const now = Date.now();
for (const item of webhookQueue) {
if (item.nextAttempt > now) continue;
try {
await deliverWebhook(item);
// Remove da fila em caso de sucesso
webhookQueue.splice(webhookQueue.indexOf(item), 1);
} catch (error) {
// Agenda retentativa com exponential backoff
item.attempts++;
item.nextAttempt = now + getBackoff(item.attempts);
if (item.attempts >= 5) {
// Marca como falhado após 5 tentativas
await markWebhookFailed(item);
webhookQueue.splice(webhookQueue.indexOf(item), 1);
}
}
}
setTimeout(processQueue, 5000);
}
function getBackoff(attempt) {
// 1min, 5min, 15min, 1hr, 4hr
const delays = [60000, 300000, 900000, 3600000, 14400000];
return delays[attempt - 1] || delays[delays.length - 1];
}
async function deliverWebhook({ webhook, event }) {
const signature = generateSignature(event, webhook.secret);
const response = await fetch(webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Petstore-Signature': signature,
'X-Petstore-Event': event.type
},
body: JSON.stringify(event),
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
}
Lado do Receptor (Cliente)
// Manuseio idempotente de webhook
const processedEvents = new Set();
app.post('/webhooks/petstore', async (req, res) => {
const event = JSON.parse(req.body.toString());
// Ignora se já processado (idempotência)
if (processedEvents.has(event.id)) {
return res.status(200).json({ received: true });
}
try {
await processEvent(event);
processedEvents.add(event.id);
// Limpa IDs de eventos antigos (mantém os últimos 1000)
if (processedEvents.size > 1000) {
const arr = Array.from(processedEvents);
arr.slice(0, arr.length - 1000).forEach(id => processedEvents.delete(id));
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
// Retorna 5xx para acionar retentativa
res.status(500).json({ error: 'Processing failed' });
}
});
async function processEvent(event) {
// Processa o evento
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
// ... manuseia outros eventos
}
}
Abordagem Híbrida
Use tanto polling quanto webhooks para atualizações críticas.
class OrderMonitor {
constructor(orderId, callback) {
this.orderId = orderId;
this.callback = callback;
this.pollInterval = null;
}
async start() {
// Começa com polling para feedback imediato
this.startPolling();
// Registra webhook para atualização em tempo real
await this.registerWebhook();
}
startPolling() {
this.pollInterval = setInterval(async () => {
const order = await this.fetchOrder();
this.callback(order);
if (['completed', 'cancelled'].includes(order.status)) {
this.stop();
}
}, 10000);
}
async registerWebhook() {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: { 'Authorization': `Bearer ${TOKEN}` },
body: JSON.stringify({
url: 'https://myapp.com/webhooks/petstore',
events: [`order.${this.orderId}`],
oneTime: true // Exclusão automática após a primeira entrega
})
});
this.webhookId = (await response.json()).id;
}
stop() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
if (this.webhookId) {
fetch(`https://petstoreapi.com/api/v1/webhooks/${this.webhookId}`, {
method: 'DELETE'
});
}
}
}
FAQ
P: Com que frequência devo fazer polling? Depende da urgência. 30 segundos para quase tempo real. 5 minutos para não urgente. Equilibre a atualidade com a carga do servidor.
P: E se o meu endpoint de webhook estiver fora do ar? Bons provedores de webhook fazem retentativas com exponential backoff. Implemente idempotência para lidar com entregas duplicadas.
P: Como eu protejo webhooks? Verifique assinaturas usando segredos compartilhados. Use apenas HTTPS. Valide os dados do evento.
P: Posso usar webhooks para dados históricos? Não. Webhooks são apenas para novos eventos. Use polling ou APIs em lote para dados históricos.
P: Devo usar polling ou webhooks para aplicativos móveis? Polling é mais simples para dispositivos móveis. Webhooks exigem notificações push como intermediário.
P: Como depuro problemas de webhook? Use ferramentas como webhook.site para testes. Registre todas as entregas de webhook. Forneça histórico de eventos de webhook em sua API.
O Modern PetstoreAPI suporta tanto polling quanto webhooks. Consulte o guia de webhooks para detalhes de implementação. Teste integrações de webhook com Apidog.
Top comments (0)