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

Ashley Innocent

Ashley Innocent

20 março 2026

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.

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: 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:

Exemplos:

Polling é adequado quando:

Quando Usar Webhooks

Bom para:

Exemplos:

Webhooks são melhores quando:

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();
}

// Usage
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 if data changed
      if (JSON.stringify(data) !== JSON.stringify(lastData)) {
        lastData = data;
        callback(data);
      }

      // Stop if condition met
      if (stopCondition(data)) {
        return;
      }

      // Reset interval on successful request
      interval = initialInterval;

    } catch (error) {
      retries++;
      if (retries >= maxRetries) {
        throw new Error('Max retries exceeded');
      }
    }

    // Schedule next poll with exponential backoff
    setTimeout(poll, interval);
    interval = Math.min(interval * 2, maxInterval);
  };

  poll();
}

// Usage: Poll order until completed
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) {
      // Not modified, continue 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 for signature verification
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;

  // Verify signature
  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());

  // Process event
  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;
  }

  // Acknowledge receipt
  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.

Pratique o design de API no Apidog

Descubra uma forma mais fácil de construir e usar APIs