DEV Community

Cover image for Webhooks vs Polling: Qual Padrão de Integração API é Melhor?
Lucas
Lucas

Posted on • Originally published at apidog.com

Webhooks vs Polling: Qual Padrão de Integração API é Melhor?

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.

Experimente o Apidog hoje

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);
Enter fullscreen mode Exit fullscreen mode

Padrões de Polling:

Polling simples:

GET /api/v1/orders/123
# Retorna o estado atual do pedido
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Polling baseado em "desde":

GET /api/v1/orders/123/events?since=1710331200
# Retorna eventos desde o timestamp
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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
  }
);
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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)
  );
}
Enter fullscreen mode Exit fullscreen mode

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"]
  }'
Enter fullscreen mode Exit fullscreen mode

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}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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'
      });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)