13 min de leituraIntegrações

Webhooks vs polling para rastreamento de cliques - escolhe o padrão certo

Uma análise prática de quando usar webhooks e quando consultar a API de análises para dados de cliques: custos ocultos de cada abordagem, exemplos de código concretos em TypeScript e Python, e o padrão híbrido que cobre a maioria dos casos de uso em produção.

Sasha Ehrlich
Compliance · EU residency
Diagrama comparativo entre webhooks e polling para rastreamento de cliques: o painel esquerdo mostra o servidor enviando eventos para um endpoint de receptor, com eventos click.created por clique planejados; o painel direito mostra o cron acessando /analytics/summary a cada N minutos

Duas equipes a construir sobre a mesma API de encurtador de URL acabam frequentemente com arquiteturas de integração completamente diferentes. Uma equipe quer um endpoint de webhook que reaja a cada clique em tempo real. A outra escreve um cron job que consulta a API de análises a cada cinco minutos. Ambas são arquiteturas válidas, mas apenas uma funciona para cliques na Elido hoje: os webhooks por clique estão planejados, não foram lançados, então o caminho disponível para dados de cliques é o polling. A decisão entre elas tem consequências reais para a latência, a sobrecarga operacional e a forma como o seu sistema se degrada quando algo corre mal.

Este artigo apresenta os trade-offs reais.

Os dois padrões

Polling

Polling significa que o seu código pede à API dados de cliques recentes de acordo com uma agenda. Um cron job acorda, chama /v1/workspaces/{workspace_id}/analytics/clicks/recent ou /v1/workspaces/{workspace_id}/analytics/summary, processa os resultados e depois dorme até ao próximo intervalo.

O fluxo de dados é por pull: a sua infraestrutura inicia todas as interações. O servidor da API não tem conhecimento dos seus sistemas internos - apenas responde às consultas que envia.

Webhooks

Webhooks significam que o servidor da Elido envia um evento para o seu endpoint HTTPS. O seu recetor trata-o, devolve um 2xx e a entrega é registrada como bem-sucedida. Hoje esses eventos abrangem links e domínios (link.created, link.updated, link.expired, domain.verified e alguns outros). Um evento click.created por clique está planejado e ainda não é emitido, então por enquanto nenhum webhook é disparado quando alguém clica.

O fluxo de dados é por push: a plataforma inicia o contato. O seu endpoint precisa de ser acessível a partir da internet, precisa de TLS e de responder de forma fiável.

Diagrama lado a lado: no polling o seu cron job envia GET para a API da Elido e vai buscar linhas a cada cinco minutos; nos webhooks o servidor da Elido envia um POST para o seu endpoint HTTPS que devolve 2xx, com o evento click.created por clique ainda planejado.

Quando o polling é a escolha certa

O polling adequa-se a um conjunto específico de condições. Se a maioria destas se aplicar à sua situação, comece com polling e só recorra a webhooks quando um problema concreto te obrigar.

Controla ambos os lados da integração. Quando o consumidor é um dashboard ou ferramenta de relatórios que possui e opera, o polling oferece um comportamento previsível e limitado. Você decide o intervalo; você decide a janela de tempo; você decide como tratar resultados parciais.

O seu caso de uso é retrospetivo. Relatórios semanais de campanha, jobs de agregação mensal e pipelines de reconciliação não beneficiam de latência abaixo do minuto. Um cron job a correr a cada hora contra /summary ou /breakdown/country é arquiteturalmente mais simples e mais fácil de raciocinar do que um recetor de webhook com estado com tratamento de tentativas.

Não tem endpoint público para expor. Os webhooks requerem um URL acessível a partir da infraestrutura da Elido. Se a sua integração corre dentro de uma rede privada, uma função Lambda sem URL estável, ou na máquina local de um programador, configurar um endpoint HTTPS de entrada pode custar mais em complexidade operacional do que o benefício de latência vale.

O volume é baixo. Com alguns milhares de cliques por dia, a diferença entre tempo real e um atraso de cinco minutos raramente é visível para os usuários finais. O polling é simples de entender, simples de depurar e não produz surpresas de infraestrutura.

Quando os webhooks são a escolha certa

Os webhooks fazem sentido quando a latência é um requisito do produto e não apenas algo agradável de ter. Para dados de cliques, leia esta seção como o plano para quando click.created for lançado; até lá, os mesmos casos de uso funcionam com um polling de intervalo curto em clicks/recent.

Está construindo um contador ao vivo ou UX em tempo real. Se o seu produto mostra aos usuários uma contagem de cliques que se atualiza visivelmente em segundos após um redirecionamento acontecer, qualquer intervalo de polling razoável parecerá visivelmente desatualizado. Um handler de webhook que incrementa um contador Redis em eventos click.created e o expõe através de uma ligação WebSocket ou SSE para o frontend será a arquitetura que alcançará isso sem martelar a API de análises, quando esse evento for lançado. Hoje, um polling de um minuto em clicks/recent que pare na última linha que já viu é o mais próximo que você pode conseguir.

Está enriquecendo registros de CRM por clique. Ligar um evento de clique a um registro de contato - identificar qual o prospeto específico que seguiu o link no seu email de outbound e atualizar a linha temporal do seu CRM - é sensível ao tempo. Quando um job de polling o apanhar cinco minutos depois, o vendedor pode já ter ligado. Um handler de webhook que dispara uma atualização do CRM em segundos após o clique será a ferramenta correta quando os eventos por clique estiverem disponíveis; até lá, mantenha o intervalo de polling curto.

Está executando fluxos de trabalho orientados a eventos. Fluxos de trabalho acionados por eventos de clique - enviar um email de acompanhamento quando um link é clicado, atualizar o segmento de um subscritor, decrementar uma contagem de inventário - são consumidores naturais de webhooks. O evento planejado click.created deve transportar dados suficientes para agir imediatamente, sem uma consulta de ida e volta. Fluxos acionados por alterações em links, como link.created ou link.expired, podem usar webhooks hoje.

Tem um endpoint HTTPS estável e acessível publicamente. Este é o pré-requisito de que tudo o resto depende. Se já tem infraestrutura de produção que aceita webhooks de entrada de outros fornecedores (Stripe, GitHub, Twilio), adicionar a Elido ao mesmo recetor tem baixo atrito.

Os custos ocultos dos webhooks

Os webhooks parecem simples: o servidor envia um POST, você trata-o. A superfície real de implementação é maior.

Diagrama em camadas das quatro portas que cada entrega de webhook de entrada atravessa: verificação de assinatura HMAC, janela de repetição de 300 segundos, deduplicação de idempotência no ID de entrega e estar disponível antes de devolver 2xx.

Verificação de assinatura

A Elido assina cada entrega de webhook com HMAC-SHA256. O formato de assinatura é v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), entregue no header X-Elido-Signature e em X-Webhook-Signature como alias. O timestamp é enviado separadamente em X-Webhook-Timestamp.

Deve verificar esta assinatura antes de processar o payload; o guia para verificar assinaturas de webhook também aborda Python, Go e rotação de segredos. Um recetor que salte a verificação processará qualquer POST que chegue ao endpoint, incluindo pedidos falsificados de qualquer pessoa que descubra o URL do seu webhook.

Aqui está um handler Express mínimo em TypeScript que verifica a assinatura antes de fazer qualquer coisa com o payload:

import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();

// Use raw body middleware - JSON parsers consume the stream before you can hash it
app.use("/webhook", express.raw({ type: "application/json" }));

function verifySignature(
  secret: string,
  signature: string,
  timestamp: string,
  rawBody: Buffer,
): boolean {
  const message = `${timestamp}.${rawBody.toString("utf8")}`;
  const expected =
    "v1=" + crypto.createHmac("sha256", secret).update(message).digest("hex");
  // Use timingSafeEqual to prevent timing-based enumeration
  return crypto.timingSafeEqual(
    Buffer.from(signature, "utf8"),
    Buffer.from(expected, "utf8"),
  );
}

app.post("/webhook", (req: Request, res: Response) => {
  const signature = req.headers["x-webhook-signature"] as string;
  const timestamp = req.headers["x-webhook-timestamp"] as string;

  if (!signature || !timestamp) {
    return res.status(400).json({ error: "missing signature headers" });
  }

  // Reject payloads older than 5 minutes
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (age > 300) {
    return res.status(400).json({ error: "payload too old" });
  }

  if (
    !verifySignature(
      process.env.WEBHOOK_SECRET!,
      signature,
      timestamp,
      req.body as Buffer,
    )
  ) {
    return res.status(401).json({ error: "invalid signature" });
  }

  const event = JSON.parse((req.body as Buffer).toString("utf8"));

  if (event.type === "link.updated") {
    // Handle the link event
    console.log("link updated:", event.data);
  }

  // Always return 2xx promptly - do heavy processing async
  return res.status(200).json({ received: true });
});

A janela de repetição

A verificação do timestamp no exemplo acima impõe o que a documentação da Elido chama de janela de repetição. Sem ela, um atacante que capture um único payload assinado válido pode repeti-lo indefinidamente - a assinatura permanece válida para sempre porque é calculada a partir de um timestamp fixo. Com a verificação, um payload com mais de cinco minutos é rejeitado independentemente de a assinatura ser válida.

Defina a tolerância para algo que a sua infraestrutura consiga lidar. Cinco minutos é o valor padrão convencional e corresponde ao que o Stripe usa. Se o seu recetor ocasionalmente fica offline durante alguns minutos durante deployments, esta janela dá-lhe tempo para voltar e ainda processar as entregas em trânsito.

Tentativas e idempotência

A Elido repete as entregas falhadas num agendamento de backoff: a primeira tentativa ocorre 5 minutos após uma falha, a segunda 15 minutos depois. Uma entrega recebe 3 tentativas no total por padrão. Após 3 tentativas falhadas, a entrega é marcada como permanentemente falhada e aparece no registro de entregas do endpoint.

Isto significa que o seu recetor pode receber o mesmo evento mais do que uma vez. Qualquer processamento que tenha efeitos secundários - escrever numa base de dados, enviar um email, atualizar um contador - precisa de ser idempotente. O header X-Webhook-Delivery transporta um ID de entrega estável que pode usar como chave de idempotência.

// Before processing, check whether this delivery has already been handled
const deliveryId = req.headers["x-webhook-delivery"] as string;
const alreadyProcessed = await redis.get(`webhook:delivery:${deliveryId}`);
if (alreadyProcessed) {
  return res.status(200).json({ received: true, duplicate: true });
}
// Mark as processed with a TTL that covers the retry window
await redis.set(`webhook:delivery:${deliveryId}`, "1", "EX", 3600);

O seu endpoint tem de ter alta disponibilidade

A janela de tentativas é finita. Se o seu recetor estiver inativo por mais de cerca de 20 minutos (5 + 15), as entregas vão esgotar as suas tentativas e falhar permanentemente. Para eventos onde a entrega garantida é importante - hooks de CRM, hooks de faturação - a sua infraestrutura de recetor precisa de disponibilidade adequada, não de um servidor de hobby que ocasionalmente reinicia.

Este é o custo mais subestimado dos webhooks para equipes novas no HTTP de entrada. O polling degrada-se graciosamente: se o job de polling falhar, simplesmente corre novamente no intervalo seguinte e recupera. Um recetor de webhook que está indisponível perde eventos permanentemente, a não ser que tenha uma estratégia de reconciliação.

Os custos ocultos do polling

O polling parece simples do exterior. Os custos reais acumulam-se em produção.

O atraso é a restrição definidora. Um cron job a correr a cada cinco minutos significa que os dados de cliques têm até cinco minutos de atraso. Para a maioria dos casos de uso retrospetivos, isto é aceitável; para qualquer coisa voltada para o usuário, não é. Encurtar o intervalo ajuda mas não elimina o atraso, e intervalos muito curtos (abaixo de um minuto) começam a parecer bombardeamento da API em vez de polling.

Pedidos desperdiçados. A maioria dos intervalos de polling devolve os mesmos dados do pedido anterior. Se está fazendo polling de um link de baixo tráfego a cada minuto e os cliques chegam a cerca de um por hora, 59 em cada 60 pedidos não devolvem nada de novo. Estes pedidos ainda contam contra o seu limite de taxa da API.

Limites de taxa. A API da Elido impõe limites de taxa por workspace dimensionados pelo nível de faturação. Um job de polling que corre frequentemente em muitos links num grande workspace pode atingir esses limites, especialmente se outra automação no mesmo workspace também estiver fazendo chamadas à API. A API devolve 429 Too Many Requests com um header X-RateLimit-Scope: workspace quando isso acontece.

Paginação e eventos perdidos. O endpoint /clicks/recent usa paginação baseada em cursor. Se faz polling numa janela de tempo fixa - ?from=<last_poll>&to=<now> - e o volume nessa janela exceder o tamanho da página, perderá eventos a não ser que siga next_cursor por todas as páginas. Uma implementação de polling que não trate a paginação irá silenciosamente perder dados sob carga.

O padrão híbrido

Para a maioria dos casos de uso em produção, a melhor resposta não é uma nem outra.

Diagrama de arquitetura híbrida: quando o evento click.created planejado for lançado, a Elido enviará eventos de clique para um handler de webhook como o caminho primário em tempo real, enquanto um polling noturno de /summary e /timeseries reconcilia os totais no seu armazenamento e preenche lacunas deixadas pela janela de tentativas.

Quando click.created for lançado, use webhooks como o caminho primário para reação em tempo real: atualizações de CRM, contadores ao vivo, fluxos de trabalho orientados a eventos. A latência é baixa; a sobrecarga operacional é gerível se já tem infraestrutura HTTPS de entrada. Até lá, o caminho rápido é um polling de intervalo curto em clicks/recent, e a passagem de reconciliação abaixo funciona da mesma forma sobre ele.

Use polling como uma passagem de reconciliação semanal ou diária: retire a série temporal completa da semana anterior, compare os totais com o que o seu handler de webhook registou e identifique quaisquer lacunas. Isto apanha entregas que esgotaram a janela de tentativas durante uma interrupção, eventos que chegaram fora de ordem e qualquer discrepância entre o seu estado local e a verdade absoluta da Elido.

A API de análises é adequada para este papel. O endpoint /summary devolve totais agregados para um intervalo de datas numa única consulta; o endpoint /timeseries devolve intervalos diários. Um job de reconciliação que corre uma vez por noite e compara as contagens de cliques registradas no seu CRM com o resumo da API para a mesma janela pode expor problemas de integridade de dados antes de se tornarem problemas visíveis para os clientes.

Um cron de polling em Python

O polling é onde toda integração de cliques começa hoje e continua funcionando depois que os webhooks por clique forem lançados. Aqui está uma implementação mínima usando a biblioteca schedule que chama clicks/recent a cada cinco minutos (o guia da API de análises de links explica em detalhes os parâmetros de cursor, data e fuso horário):

import schedule
import time
import requests
import os

API_BASE = "https://api.elido.app/v1"
WORKSPACE_ID = os.environ["ELIDO_WORKSPACE_ID"]
API_KEY = os.environ["ELIDO_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Newest click already processed, kept across runs. clicks/recent returns
# newest first and next_cursor pages to older rows, so each run reads from
# the top and stops when it reaches this row.
_last_seen = None

def poll_recent_clicks():
    global _last_seen
    params = {"limit": 100}
    newest = None
    done = False

    while not done:
        resp = requests.get(
            f"{API_BASE}/workspaces/{WORKSPACE_ID}/analytics/clicks/recent",
            headers=HEADERS,
            params=params,
            timeout=10,
        )
        resp.raise_for_status()
        body = resp.json()

        for click in body.get("items", []):
            key = (click["ts"], click["link_id"])
            if key == _last_seen:
                done = True
                break
            if newest is None:
                newest = key
            process_click(click)

        next_cursor = body.get("next_cursor")
        if not next_cursor:
            break
        params["cursor"] = next_cursor

    if newest:
        _last_seen = newest

def process_click(click: dict):
    # Replace with your actual processing logic
    print(f"click: link={click['link_id']} country={click.get('country_code')}")

schedule.every(5).minutes.do(poll_recent_clicks)

if __name__ == "__main__":
    poll_recent_clicks()  # run once on startup to catch up
    while True:
        schedule.run_pending()
        time.sleep(10)

Num deployment de produção, substitua o print pelo seu sink real - uma escrita em base de dados, uma chamada à API de CRM, uma publicação numa fila de mensagens - e adicione tratamento de erros com backoff exponencial à chamada requests.get.

Filtragem de bots e o que significa para a sua integração

Um detalhe que afeta ambos os padrões: a camada de redirecionamento da Elido filtra cliques de bots antes que sejam registrados. Pedidos do Googlebot, Bingbot, Slackbot, monitores de uptime, curl, bibliotecas de scripting e User-Agents vazios não são contados como cliques e não aparecem nos resultados da API de análises, e não produzirão eventos click.created quando estes forem lançados.

Isto é importante porque significa que o seu handler de webhook ou job de polling está trabalhando com contagens de redirecionamentos humanos, não com contagens brutas de pedidos HTTP. Se está correlacionando dados de cliques da Elido com métricas do lado do servidor - os logs do servidor da sua aplicação, os logs de acesso de um CDN - espera que os números da Elido sejam mais baixos. A discrepância não é um bug; é o filtro de bots a remover ruído antes que chegue a você.

Para mais detalhes sobre o que o filtro de bots cobre e como o pontuador de suspeição marca o tráfego limítrofe, o guia de análises tem uma análise completa. Para as propriedades de segurança do esquema de assinatura de webhooks - incluindo o formato HMAC, a vinculação de timestamp e o que previne - consulte a lista de verificação de segurança.


A página de preços tem a desagregação de quais os níveis de plano que incluem endpoints de webhook e com que limites de volume de entrega.

Relacionados no blog

Experimente Elido

Cole uma URL, obtenha um link curto

Sem cadastro. O link vive 30 dias. Cadastre-se para mantê-lo para sempre.

Grátis, sem necessidade de registo · 2 por dia

Experimente o Elido

Encurtador de URL hospedado na UE: domínios personalizados, análises profundas e API aberta. Plano gratuito - sem cartão de crédito.

Tags
click tracking
webhooks
url shortener api
link analytics
api integration
event-driven
polling
real-time analytics

Continuar lendo