Os webhooks de encurtador de URL do Elido enviam via POST um envelope JSON assinado para seu endpoint HTTPS sempre que algo muda em um workspace: um link é criado, editado, excluído, expira ou atinge seu limite de clique, um membro é convidado, um domínio se verifica. Cada requisição carrega um header X-Webhook-Signature: v1=<hex>, que é HMAC-SHA256 sobre {timestamp}.{raw_body}, e uma entrega que falha recebe três tentativas em cerca de vinte minutos.
O que ele não envia hoje é um webhook de clique em link. Os cliques saem pela API de analytics e pelos encaminhadores de evento, e vou mostrar onde no final. Este post é a metade de saída da superfície da API; o guia rápido da API + SDKs do encurtador de URL cobre a metade de entrada, e smart links explicados é o post principal de recursos de onde os eventos de link vêm.
Quais eventos de link disparam um webhook hoje
Todo evento abaixo chega a um endpoint de webhook que o assinou pelo nome. O formulário de novo endpoint do painel oferece checkboxes para os oito mais comuns; a API aceita qualquer nome da lista.
| Evento | Dispara quando | Checkbox no painel |
|---|---|---|
link.created | Um link é criado, um por um ou em uma importação em massa | sim |
link.updated | Destino, configurações ou status mudam, edições em massa, restaurações | sim |
link.deleted | Um link é excluído, individualmente ou em massa | sim |
link.expired | Um link passa da data de expiração | só na API |
link.cap_reached | Um link atinge sua contagem máxima de clique | só na API |
link.broken | A checagem de link quebrado vê o destino falhando | só na API |
workspace.created, workspace.updated | Um workspace é provisionado ou suas configurações mudam | sim |
member.invited, member.removed | Um membro é adicionado (diretamente, por SCIM ou um convite aceito) ou removido | sim |
member.role_changed | O papel de um membro muda | só na API |
invitation.created, invitation.accepted | Um convite é enviado ou aceito | só na API |
domain.verified, domain.ssl_failed | Um domínio personalizado passa nas checagens de DNS, ou continua falhando após 24 horas | só na API |
audit.event | Qualquer entrada de log de auditoria | sim |
Links criptografados adicionam mais dois, link.encrypted_created e link.encryption_rotated. Se você preferir não manter a lista atualizada manualmente, crie um endpoint siem: ele recebe todo evento do workspace, entradas de auditoria incluídas, sem nenhum filtro de assinatura.
No roadmap, ainda não ao vivo: click.created (um fluxo de clique amostrado), eventos de billing como billing.subscription_upgraded, e filtros por endpoint como "só links na pasta X". Não assine esses nomes hoje; nada os publica.
Criando um endpoint de webhook com a API
Um endpoint pertence a um workspace. Você o cria com um POST para /v1/workspaces/{workspace_id}/webhooks:
curl -X POST "https://api.elido.app/v1/workspaces/$WORKSPACE_ID/webhooks" \
-H "Authorization: Bearer elido_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"description": "CRM sync",
"kind": "event"
}'
A resposta é 201 com o endpoint e, para os tipos event e siem, um segredo único:
{
"endpoint": {
"id": 7,
"workspace_id": 42,
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"is_active": true,
"description": "CRM sync",
"kind": "event",
"config": {},
"created_at": "2026-09-21T09:12:44Z"
},
"secret": "whsec_9f2c..."
}
O Elido gera o segredo; você não envia um. Copie-o agora, porque nenhuma chamada posterior o retorna. kind aceita cinco valores. event e siem são as entregas JSON assinadas que este post cobre. discord, telegram e sentry remodelam os mesmos eventos em uma mensagem de chat ou um evento do Sentry, autenticam pela URL ou por um token de bot criptografado, e não carregam headers de HMAC.
As permissões mudaram este mês. Ler endpoints e o log de entrega precisa de workspace.view. Criar, editar, excluir, rotacionar um segredo ou reenviar uma entrega precisa de workspace.edit, o que significa um administrador ou proprietário. Uma chave de API funciona dentro do workspace para o qual foi emitida e nunca acima do papel escolhido quando foi criada, então uma chave de nível viewer recebe um 403 no POST acima. Referência completa: a documentação de webhooks.
O envelope de payload do webhook
Toda entrega assinada tem o mesmo envelope de quatro campos. data guarda o registro que mudou, então para eventos de link é a linha do link:
{
"type": "link.created",
"workspace_id": 42,
"data": {
"id": 91834,
"workspace_id": 42,
"domain_id": 3,
"slug": "spring-sale",
"destination_url": "https://shop.example.com/spring",
"title": "Spring sale landing",
"tags": ["newsletter"],
"status": "active",
"expires_at": null,
"max_clicks": null,
"redirect_status": 302,
"created_by_user_id": 17,
"created_at": "2026-09-21T09:14:02.184311Z"
},
"timestamp": "2026-09-21T09:14:02Z"
}
Essa amostra está reduzida; o data real carrega toda coluna do link, incluindo regras de segmentação, pasta, campanha e campos de scan. Não há ID de evento nem short_url no corpo, então monte a URL curta a partir do seu domínio e do slug se precisar. Eventos agendados enviam um objeto menor: link.expired tem link_id, slug e destination_url, e link.cap_reached adiciona cap e clicks.
Uma correção que você deveria conhecer se registrava payloads antes desta semana: campos secretos agora são removidos antes de um payload sair do Elido. O password_hash de um link protegido por senha e o token de um convite costumavam aparecer em data; não aparecem mais, nem em entregas novas nem no log de entrega. Se você armazenou payloads antigos, vale a pena purgar esses dados.
Verificando o header X-Webhook-Signature
Cada requisição assinada carrega estes headers:
X-Webhook-Signature: v1=5d8f0c3e...
X-Elido-Signature: v1=5d8f0c3e...
X-Webhook-Timestamp: 1790068442
X-Webhook-Event: link.created
X-Webhook-Delivery: 55120
User-Agent: Elido-Webhooks/1.0
Os dois headers de assinatura guardam o mesmo valor. O Elido calcula HMAC-SHA256, conforme definido na RFC 2104, com a string whsec_... inteira como chave, sobre o timestamp, um ponto, e os bytes brutos do corpo. O digest hex recebe um prefixo v1=. Não há campo t= dentro do header; o timestamp vive no próprio header.
No Node, assine os bytes brutos, não um objeto reserializado. O Express precisa de express.raw({ type: "application/json" }) nessa rota por esse motivo:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyElido(secret, headers, rawBody, toleranceSec = 300) {
const ts = headers["x-webhook-timestamp"] ?? "";
if (!/^\d+$/.test(ts)) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false;
const mac = createHmac("sha256", secret).update(`${ts}.`).update(rawBody);
const expected = Buffer.from("v1=" + mac.digest("hex"));
// During a rotation the old secret signs X-Elido-Signature-Previous.
return ["x-elido-signature", "x-elido-signature-previous"].some((name) => {
const got = Buffer.from(headers[name] ?? "");
return got.length === expected.length && timingSafeEqual(got, expected);
});
}
A checagem de comprimento importa: o timingSafeEqual do Node lança um erro em buffers de tamanhos diferentes em vez de retornar false. A versão em Python com o módulo padrão hmac:
import hashlib
import hmac
import time
def verify_elido(secret: str, headers, raw_body: bytes, tolerance: int = 300) -> bool:
ts = headers.get("X-Webhook-Timestamp", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > tolerance:
return False
digest = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256)
expected = "v1=" + digest.hexdigest()
for name in ("X-Elido-Signature", "X-Elido-Signature-Previous"):
got = headers.get(name)
if got and hmac.compare_digest(got, expected):
return True
return False
Se a verificação continuar falhando, o guia de verificação de assinaturas de webhook tem uma versão em Go, um nó Code do n8n e as causas mais comuns de incompatibilidade. A janela de cinco minutos é sua checagem, não a nossa. O Elido carimba um timestamp novo em cada tentativa, incluindo retries, então uma nova tentativa legítima nunca parece antiga. Sem a janela, qualquer um que tenha capturado uma requisição poderia reproduzi-la na semana seguinte e a assinatura ainda bateria.
A rotação é POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret, ou o botão Rotate na página do endpoint. Você recebe o novo segredo uma vez. Pelos próximos sete dias, cada entrega também carrega X-Elido-Signature-Previous, assinado com o segredo antigo, motivo pelo qual as duas funções acima o testam. Implante o novo segredo a qualquer momento naquela semana e nada falha.
A política de retry do webhook
O worker de entrega pega entregas pendentes a cada poucos segundos, então uma mudança de link geralmente chega até você em segundos. Qualquer resposta 2xx marca a entrega como concluída. Um status não-2xx, um erro de rede ou nenhuma resposta em 10 segundos conta como uma tentativa falhada.
Três tentativas por entrega, vinte minutos de ponta a ponta. Isso é curto de propósito, e honestamente é mais curto do que eu escolheria para um receptor atrás de uma VPN instável. Uma indisponibilidade de duas horas do seu lado não será coberta por retries automáticos. O que cobre isso é o log de entrega: GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries lista cada entrega com status, código HTTP, latência, contagem de tentativas e horário da próxima tentativa, e a página do endpoint mostra as mesmas linhas com um botão Retry. Retry, ou POST .../deliveries/{delivery_id}/retry, rearma uma entrega falhada ou entregue com um orçamento novo de três tentativas e retorna 202. Uma entrega ainda pendente recebe 409.
Algumas falhas pulam as tentativas. Um endpoint do Telegram sem chat_id, ou um DSN de Sentry malformado, é marcado como falhado imediatamente, porque repetir a requisição não vai corrigir a configuração. Para tirar um endpoint do ar sem excluí-lo, envie PUT com "is_active": false; endpoints pausados não recebem novas entregas.
Se seu handler faz trabalho pesado, retorne 200 primeiro e enfileire o job. O corte de dez segundos é onde um handler lento mas bem-sucedido vira uma duplicata, o que nos leva à deduplicação.
Planejando um receptor para sua equipe? A página do recurso de webhooks mostra o lado do painel de tudo isso.
Idempotência e ordenação de webhook
O Elido entrega pelo menos uma vez. A seção de retry mostrou uma forma de uma duplicata acontecer: seu handler faz o commit do trabalho, depois perde a janela de dez segundos, e o Elido o envia de novo. Um Retry manual reenvia de propósito.
Os dois casos mantêm o mesmo valor de X-Webhook-Delivery, porque é o ID de uma entrega para um endpoint, não de uma tentativa. Chaveie por ele:
CREATE TABLE elido_webhook_seen (
delivery_id BIGINT PRIMARY KEY,
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- in the handler, inside the same transaction as your work:
INSERT INTO elido_webhook_seen (delivery_id) VALUES ($1)
ON CONFLICT (delivery_id) DO NOTHING
RETURNING delivery_id;
-- no row back means you've already handled this delivery
Dois endpoints assinados no mesmo evento recebem dois IDs de entrega diferentes, então deduplique por endpoint. Em raros reinícios do nosso lado, um evento pode ser enfileirado duas vezes como entregas separadas; se uma escrita dupla causasse dano, adicione uma segunda proteção em type mais data.id mais data.updated_at.
Não há garantia de ordenação. As entregas saem na ordem de vencimento mais antiga primeiro, mas uma nova tentativa de um evento anterior pode chegar depois de um mais recente. Compare data.updated_at contra o que você já armazenou antes de sobrescrever um link, e não confie no timestamp do envelope para ordenação: ele só tem precisão de um segundo.
Onde vivem os dados de clique em vez de um webhook de clique
Esta é a parte que a versão mais antiga deste post errou. Não existe um webhook click hoje, e click.created está planejado, não lançado. O caminho de redirecionamento é mantido livre de trabalho síncrono, o que o post ingestão de clique fire-and-forget explica, e os cliques vão para o armazenamento de analytics em vez da fila de webhook.
Para dados no nível de clique agora, você tem duas rotas:
- Encaminhadores de evento. Cada clique em link curto vira um evento server-side na ferramenta que você já usa: eventos de clique de link do Mixpanel, eventos de clique do Klaviyo em perfis, ou métricas de redirecionamento do Datadog para painéis de operações.
- A API de analytics.
GET /v1/analytics/workspaces/{workspace_id}/clicks/recentretorna cliques recentes, eclicks.csvos exporta, para que um job agendado possa puxar as linhas de que precisa.
Qual se encaixa depende de latência e de onde os dados acabam indo; webhooks vs polling para rastreamento de clique percorre essa troca. E se o fluxo amostrado click.created for lançado, esta página vai avisar primeiro.
Leia o conteúdo principal: smart links explicados.
Relacionados no blog
- Webhooks vs polling para rastreamento de clique - quando empurrar e quando puxar.
- Guia rápido da API + SDKs do encurtador de URL - a superfície de API de entrada.
- Ingestão de clique fire-and-forget - por que os cliques nunca esperam por chamadas de saída.
- Eventos de clique de link do Mixpanel - dados de clique como eventos server-side.
- Métricas de redirecionamento de link do Datadog - saúde de redirecionamento em um painel de operações.
- Verificação de assinaturas de webhook - verificações HMAC em Node, Python, Go e n8n, além da depuração de incompatibilidades.
Perguntas frequentes
O Elido envia um webhook para cada clique em link?
Não hoje. Os webhooks cobrem mudanças de workspace como link.created, link.updated, link.expired e member.invited. Um evento click.created está no roadmap como um fluxo amostrado. Para dados no nível de clique agora, use a API de analytics ou um encaminhador de evento como Mixpanel, Klaviyo ou Datadog.
Como verifico uma assinatura de webhook do Elido?
Calcule HMAC-SHA256 sobre o valor de X-Webhook-Timestamp, um ponto e o corpo bruto da requisição, chaveado com o seu segredo whsec_. Codifique em hex, adicione o prefixo v1= e compare em tempo constante com o header X-Webhook-Signature. Rejeite timestamps com mais de cinco minutos.
Quantas vezes o Elido tenta novamente um webhook que falhou?
Cada entrega recebe três tentativas: uma imediata, uma cinco minutos depois de uma falha e outra quinze minutos depois disso. Qualquer status não-2xx, erro de rede ou resposta mais lenta que dez segundos conta como falha. Depois da terceira, a entrega é marcada como falhada até você clicar em Retry.
Qual é a diferença entre X-Webhook-Signature e X-Elido-Signature?
Nenhuma além do nome. Os dois headers carregam a mesma assinatura v1=, e X-Webhook-Signature permanece para receptores mais antigos. Durante uma rotação de segredo o Elido também envia X-Elido-Signature-Previous, assinado com o segredo antigo, por sete dias.
Como paro de processar o mesmo webhook duas vezes?
Armazene o valor do header X-Webhook-Delivery e pule qualquer requisição cujo valor você já tenha tratado. Ele identifica uma entrega para um endpoint e permanece o mesmo entre novas tentativas automáticas e reenvios manuais, então um índice único nele é suficiente.
Quem pode criar ou excluir webhooks em um workspace?
Administradores e proprietários do workspace. Listar endpoints e ler o log de entrega precisa de acesso de visualização; criar, editar, excluir, rotacionar um segredo ou reenviar uma entrega precisa da permissão workspace.edit. Chaves de API ficam limitadas ao papel escolhido quando a chave foi criada.
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