Duas equipas a construir sobre a mesma API de encurtador de URL acabam frequentemente com arquiteturas de integração completamente diferentes. Uma equipa configura um endpoint de webhook e reage 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 válidas. A decisão entre elas tem consequências reais para a latência, a sobrecarga operacional e a forma como o teu sistema se degrada quando algo corre mal.
Este artigo apresenta os trade-offs reais.
Os dois padrões#
Polling#
Polling significa que o teu código pede à API dados de cliques recentes de acordo com uma agenda. Um cron job acorda, chama /v1/analytics/workspaces/{id}/clicks/recent ou /v1/analytics/workspaces/{id}/summary, processa os resultados e depois dorme até ao próximo intervalo.
O fluxo de dados é por pull: a tua infraestrutura inicia todas as interações. O servidor da API não tem conhecimento dos teus sistemas internos - apenas responde às consultas que envias.
Webhooks#
Webhooks significam que o servidor da Elido envia um evento click.recorded para o teu endpoint HTTPS pouco depois de um clique ser processado. O teu recetor trata-o, devolve um 2xx e a entrega é registada como bem-sucedida.
O fluxo de dados é por push: a plataforma inicia o contacto. O teu endpoint precisa de ser acessível a partir da internet, precisa de TLS e de responder de forma fiável.
Quando o polling é a escolha certa#
O polling adequa-se a um conjunto específico de condições. Se a maioria destas se aplicar à tua situação, começa com polling e só recorre a webhooks quando um problema concreto te obrigar.
Controlas ambos os lados da integração. Quando o consumidor é um dashboard ou ferramenta de relatórios que possuis e operas, o polling oferece um comportamento previsível e limitado. Tu decides o intervalo; tu decides a janela de tempo; tu decides como tratar resultados parciais.
O teu 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 tens endpoint público para expor. Os webhooks requerem um URL acessível a partir da infraestrutura da Elido. Se a tua 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 utilizadores 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.
Estás a construir um contador ao vivo ou UX em tempo real. Se o teu produto mostra aos utilizadores 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.recorded e o expõe através de uma ligação WebSocket ou SSE para o frontend é a arquitetura que consegue isto sem martelas a API de análises.
Estás a enriquecer registos de CRM por clique. Ligar um evento de clique a um registo de contacto - identificar qual o prospeto específico que seguiu o link no teu 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 é a ferramenta correta.
Estás a executar 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 click.recorded transporta dados suficientes para agir imediatamente, sem uma consulta de ida e volta.
Tens um endpoint HTTPS estável e acessível publicamente. Este é o pré-requisito de que tudo o resto depende. Se já tens 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, tu tratas-o. A superfície real de implementação é maior.
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-Webhook-Signature. O timestamp é enviado separadamente em X-Webhook-Timestamp. Ambos são produzidos em services/webhook-dispatcher/internal/signing/hmac.go.
Deves verificar esta assinatura antes de processar o payload. 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 teu 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 === "click.recorded") {
// Handle the click event
console.log("click recorded:", 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.
Define a tolerância para algo que a tua infraestrutura consiga lidar. Cinco minutos é o valor padrão convencional e corresponde ao que o Stripe usa. Se o teu 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#
O webhook-dispatcher da Elido repete as entregas falhadas num agendamento de backoff: primeira tentativa ao fim de 1 minuto, segunda ao fim de 5 minutos, terceira ao fim de 15 minutos. O valor máximo de tentativas por entrega é 3 por padrão, conforme definido no schema webhook_deliveries. Após 3 tentativas falhadas, a entrega é marcada como permanentemente falhada e aparece no dashboard de notificações.
Isto significa que o teu 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 podes 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 teu endpoint tem de ter alta disponibilidade#
A janela de tentativas é finita. Se o teu recetor estiver inativo por mais de cerca de 21 minutos (1 + 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 tua 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 equipas 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 tenhas 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 utilizador, 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ás a fazer 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 teu 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 a fazer 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 fazes polling numa janela de tempo fixa - ?from=<last_poll>&to=<now> - e o volume nessa janela exceder o tamanho da página, perderás eventos a não ser que sigas 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.
Usa 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á tens infraestrutura HTTPS de entrada.
Usa polling como uma passagem de reconciliação semanal ou diária: retira a série temporal completa da semana anterior, compara os totais com o que o teu handler de webhook registou e identifica 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 teu 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 registadas no teu 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#
Para equipas que queiram começar com polling e migrar para webhooks mais tarde, aqui está uma implementação mínima usando a biblioteca schedule que chama /clicks/recent a cada cinco minutos:
import schedule
import time
import requests
import os
API_BASE = "https://api.elido.app/v1/analytics"
WORKSPACE_ID = os.environ["ELIDO_WORKSPACE_ID"]
API_KEY = os.environ["ELIDO_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Track the cursor across poll intervals so we only fetch new clicks
_cursor = None
def poll_recent_clicks():
global _cursor
params = {"limit": 100}
if _cursor:
params["cursor"] = _cursor
while True:
resp = requests.get(
f"{API_BASE}/workspaces/{WORKSPACE_ID}/clicks/recent",
headers=HEADERS,
params=params,
timeout=10,
)
resp.raise_for_status()
body = resp.json()
items = body.get("items", [])
for click in items:
process_click(click)
next_cursor = body.get("next_cursor")
if not next_cursor:
# Persist the current cursor for the next run
if items:
_cursor = None # reset: next poll fetches from now
break
params["cursor"] = next_cursor
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, substitui o print pelo teu sink real - uma escrita em base de dados, uma chamada à API de CRM, uma publicação numa fila de mensagens - e adiciona tratamento de erros com backoff exponencial à chamada requests.get.
Filtragem de bots e o que significa para a tua integração#
Um detalhe que afeta ambos os padrões: o serviço edge-redirect da Elido filtra cliques de bots antes de emitir eventos de clique para o pipeline de processamento. Pedidos do Googlebot, Bingbot, Slackbot, monitores de uptime, curl, bibliotecas de scripting e User-Agents vazios não produzem eventos click.recorded e não aparecem nos resultados da API de análises.
Isto é importante porque significa que o teu handler de webhook ou job de polling está a trabalhar com contagens de redirecionamentos humanos, não com contagens brutas de pedidos HTTP. Se estás a correlacionar dados de cliques da Elido com métricas do lado do servidor - os logs do servidor da tua 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 ti.
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 - consulta 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