I webhook per gli URL shortener di Elido inviano tramite POST un involucro JSON firmato al tuo endpoint HTTPS ogni volta che qualcosa cambia in un workspace: un link viene creato, modificato, eliminato, scade o raggiunge il suo tetto di click, un membro viene invitato, un dominio si verifica. Ogni richiesta porta un header X-Webhook-Signature: v1=<hex>, che è un HMAC-SHA256 su {timestamp}.{raw_body}, e una consegna fallita riceve tre tentativi in circa venti minuti.
Ciò che non invia oggi è un webhook per il click su un link. I click escono invece attraverso l'API di analytics e gli inoltratori di eventi, e mostrerò dove alla fine. Questo articolo è la metà in uscita della superficie API; la guida rapida API + SDK dell'URL shortener copre la metà in entrata, e smart link spiegati è il cornerstone delle funzionalità da cui provengono gli eventi sui link.
Quali eventi sui link generano un webhook oggi
Ogni evento sotto raggiunge un endpoint webhook che vi si è iscritto per nome. Il form per un nuovo endpoint nella dashboard offre caselle di spunta per gli otto più comuni; l'API accetta qualsiasi nome dell'elenco.
| Evento | Scatta quando | Casella dashboard |
|---|---|---|
link.created | Un link viene creato, uno alla volta o in un import in blocco | sì |
link.updated | Cambia la destinazione, le impostazioni o lo stato, modifiche in blocco, ripristini | sì |
link.deleted | Un link viene eliminato, singolarmente o in blocco | sì |
link.expired | Un link supera la sua data di scadenza | solo API |
link.cap_reached | Un link raggiunge il suo conteggio massimo di click | solo API |
link.broken | Il controllo dei link non funzionanti rileva che la destinazione fallisce | solo API |
workspace.created, workspace.updated | Un workspace viene creato o le sue impostazioni cambiano | sì |
member.invited, member.removed | Un membro viene aggiunto (direttamente, via SCIM o tramite un invito accettato) o rimosso | sì |
member.role_changed | Il ruolo di un membro cambia | solo API |
invitation.created, invitation.accepted | Un invito viene inviato o accettato | solo API |
domain.verified, domain.ssl_failed | Un dominio personalizzato supera i controlli DNS, o continua a fallirli dopo 24 ore | solo API |
audit.event | Qualsiasi voce del log di audit | sì |
I link cifrati aggiungono altri due eventi, link.encrypted_created e link.encryption_rotated. Se preferisci non tenere l'elenco aggiornato a mano, crea un endpoint siem: riceve ogni evento del workspace, voci di audit incluse, senza alcun filtro di sottoscrizione.
Nella roadmap, non ancora attivi: click.created (un flusso campionato di click), eventi di fatturazione come billing.subscription_upgraded, e filtri per singolo endpoint come "solo link nella cartella X". Non iscriverti oggi a questi nomi; nessuno li pubblica ancora.
Creare un endpoint webhook con l'API
Un endpoint appartiene a un workspace. Lo crei con una POST a /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"
}'
La risposta è 201 con l'endpoint e, per i tipi event e siem, un secret monouso:
{
"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..."
}
Elido genera il secret; tu non ne invii uno. Copialo subito, perché nessuna chiamata successiva lo restituisce. kind accetta cinque valori. event e siem sono le consegne JSON firmate trattate in questo articolo. discord, telegram e sentry rimodellano gli stessi eventi in un messaggio di chat o in un evento Sentry, si autenticano tramite l'URL o un token bot cifrato, e non trasportano header HMAC.
I permessi sono cambiati questo mese. Leggere gli endpoint e il log di consegna richiede workspace.view. Creare, modificare, eliminare, ruotare un secret o reinviare una consegna richiede workspace.edit, il che significa un amministratore o un proprietario. Una chiave API funziona all'interno del workspace per cui è stata emessa e mai al di sopra del ruolo scelto al momento della creazione, quindi una chiave a livello viewer riceve un 403 sulla POST qui sopra. Riferimento completo: la documentazione sui webhook.
L'involucro del payload webhook
Ogni consegna firmata ha lo stesso involucro a quattro campi. data contiene il record che è cambiato, quindi per gli eventi sui link è la riga del 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"
}
Quel campione è ridotto; il vero data porta ogni colonna del link, incluse le regole di targeting, la cartella, la campagna e i campi di scansione. Non c'è alcun ID evento né uno short_url nel corpo, quindi costruisci l'URL breve a partire dal tuo dominio e dallo slug se ti serve. Gli eventi pianificati inviano un oggetto più piccolo: link.expired ha link_id, slug e destination_url, e link.cap_reached aggiunge cap e clicks.
Una correzione che dovresti conoscere se hai registrato i payload prima di questa settimana: i campi segreti ora vengono rimossi prima che un payload lasci Elido. password_hash di un link protetto da password e token di un invito prima comparivano in data; ora non più, né nelle nuove consegne né nel log di consegna. Se hai memorizzato vecchi payload, quei dati vanno eliminati.
Verificare l'header X-Webhook-Signature
Ogni richiesta firmata porta questi header:
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
I due header di firma contengono lo stesso valore. Elido calcola HMAC-SHA256, come definito nell'RFC 2104, con l'intera stringa whsec_... come chiave, sul timestamp, un punto e i byte grezzi del corpo. Il digest esadecimale riceve un prefisso v1=. Non c'è alcun campo t= dentro l'header; il timestamp vive nel proprio header separato.
In Node, firma i byte grezzi, non un oggetto riserializzato. Express richiede express.raw({ type: "application/json" }) su questa rotta per questo 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);
});
}
Il controllo di lunghezza è importante: timingSafeEqual di Node lancia un'eccezione su buffer di dimensioni diverse invece di restituire false. La versione Python con il modulo standard 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 il controllo continua a non riuscire, la guida alla verifica delle firme dei webhook include una versione in Go, un nodo Code di n8n e le cause più comuni di una mancata corrispondenza. La finestra di cinque minuti è un controllo tuo, non nostro. Elido appone un timestamp fresco a ogni tentativo, retry inclusi, quindi un retry legittimo non sembra mai obsoleto. Senza la finestra, chiunque abbia catturato una richiesta potrebbe riprodurla la settimana successiva e la firma corrisponderebbe comunque.
La rotazione avviene con POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret, oppure con il pulsante Rotate sulla pagina dell'endpoint. Ottieni il nuovo secret una sola volta. Per i sette giorni successivi ogni consegna porta anche X-Elido-Signature-Previous, firmato con il vecchio secret, motivo per cui entrambe le funzioni sopra lo provano. Distribuisci il nuovo secret in un qualsiasi momento di quella settimana e niente si rompe.
La policy di retry dei webhook
Il worker di consegna preleva le consegne in attesa ogni pochi secondi, quindi una modifica a un link di solito ti raggiunge entro pochi secondi. Qualsiasi risposta 2xx segna la consegna come completata. Uno stato non-2xx, un errore di rete o l'assenza di risposta entro 10 secondi conta come tentativo fallito.
Tre tentativi per consegna, venti minuti end to end. È volutamente breve, e onestamente è più breve di quanto sceglierei per un ricevitore dietro una VPN instabile. Un'interruzione di due ore dal tuo lato non sarà coperta dai retry automatici. Ciò che la copre è il log di consegna: GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries elenca ogni consegna con stato, codice HTTP, latenza, numero di tentativi e prossimo orario di retry, e la pagina dell'endpoint mostra le stesse righe con un pulsante Retry. Retry, o POST .../deliveries/{delivery_id}/retry, riarma una consegna fallita o completata con un budget fresco di tre tentativi e restituisce 202. Una consegna ancora in sospeso riceve 409.
Alcuni fallimenti saltano i retry. Un endpoint Telegram privo del suo chat_id, o un DSN Sentry malformato, viene contrassegnato come fallito immediatamente, perché ripetere la richiesta non risolverà un problema di configurazione. Per mettere offline un endpoint senza eliminarlo, invia una PUT con "is_active": false; gli endpoint in pausa non ricevono nuove consegne.
Se il tuo handler svolge un lavoro pesante, restituisci prima 200 e metti in coda il job. Il taglio dei dieci secondi è il punto in cui un handler lento ma riuscito si trasforma in un duplicato, il che ci porta alla deduplicazione.
Stai progettando un ricevitore per il tuo team? La pagina delle funzionalità webhook mostra il lato dashboard di tutto questo.
Idempotenza e ordinamento dei webhook
Elido consegna at-least-once. La sezione sui retry ha mostrato un modo in cui avviene un duplicato: il tuo handler completa il lavoro, poi manca la finestra di dieci secondi, ed Elido lo invia di nuovo. Un Retry manuale reinvia intenzionalmente.
Entrambi i casi mantengono lo stesso valore di X-Webhook-Delivery, perché è l'ID di una consegna verso un endpoint, non di un tentativo. Usalo come chiave:
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
Due endpoint iscritti allo stesso evento ottengono due ID di consegna diversi, quindi deduplica per endpoint. In rari riavvii dal nostro lato un evento può essere accodato due volte come consegne separate; se una doppia scrittura farebbe danno, aggiungi una seconda protezione su type più data.id più data.updated_at.
Non c'è alcuna garanzia di ordinamento. Le consegne partono in ordine di scadenza più vecchia prima, ma il retry di un evento precedente può arrivare dopo uno più recente. Confronta data.updated_at con quanto hai già memorizzato prima di sovrascrivere un link, e non fare affidamento sul timestamp dell'involucro per l'ordinamento: ha una precisione di un solo secondo.
Dove vivono i dati sui click al posto di un webhook di click
Questa è la parte che la versione precedente di questo articolo sbagliava. Non esiste oggi un webhook click, e click.created è pianificato, non pubblicato. Il percorso di redirect resta libero da lavoro sincrono, cosa che l'articolo ingestione dei click fire-and-forget spiega, e i click vanno verso l'archiviazione analytics piuttosto che nella coda dei webhook.
Per i dati a livello di click oggi, hai due strade:
- Inoltratori di eventi. Ogni click su un link breve diventa un evento lato server nello strumento che già usi: eventi di click su link in Mixpanel, eventi di click su profili in Klaviyo, o metriche di redirect in Datadog per dashboard operative.
- L'API di analytics.
GET /v1/analytics/workspaces/{workspace_id}/clicks/recentrestituisce i click recenti, eclicks.csvli esporta, così un job pianificato può estrarre le righe di cui ha bisogno.
Quale scegliere dipende dalla latenza e da dove finiscono i dati; l'articolo webhook contro polling per il tracciamento dei click ripercorre quel compromesso. E se il flusso campionato click.created verrà pubblicato, questa pagina lo dirà per prima.
Leggi il cornerstone: smart link spiegati.
Correlati sul blog
- Webhook contro polling per il tracciamento dei click - quando fare push e quando fare pull.
- Guida rapida API + SDK dell'URL shortener - la superficie API in entrata.
- Ingestione dei click fire-and-forget - perché i click non aspettano mai chiamate in uscita.
- Eventi di click su link in Mixpanel - i dati di click come eventi lato server.
- Metriche di redirect dei link in Datadog - la salute del redirect su una dashboard operativa.
- Verifica delle firme dei webhook - controlli HMAC in Node, Python, Go e n8n, oltre al debug di una mancata corrispondenza.
Domande frequenti
Elido invia un webhook per ogni click su un link?
Non oggi. I webhook coprono i cambiamenti a livello di workspace come link.created, link.updated, link.expired e member.invited. Un evento click.created è nella roadmap come flusso campionato. Per i dati a livello di click oggi, usa l'API di analytics o un inoltratore di eventi come Mixpanel, Klaviyo o Datadog.
Come verifico la firma di un webhook Elido?
Calcola HMAC-SHA256 sul valore di X-Webhook-Timestamp, un punto e il corpo grezzo della richiesta, con chiave il tuo secret whsec_. Codificalo in esadecimale, aggiungi il prefisso v1= e confrontalo a tempo costante con l'header X-Webhook-Signature. Rifiuta i timestamp più vecchi di cinque minuti.
Quante volte Elido riprova un webhook fallito?
Ogni consegna riceve tre tentativi: uno immediato, uno cinque minuti dopo un fallimento e uno quindici minuti dopo quello. Qualsiasi stato non-2xx, errore di rete o risposta più lenta di dieci secondi conta come fallimento. Dopo il terzo, la consegna viene contrassegnata come fallita finché non premi Retry.
Qual è la differenza tra X-Webhook-Signature e X-Elido-Signature?
Nessuna, a parte il nome. Entrambi gli header trasportano la stessa firma v1=, e X-Webhook-Signature resta per i ricevitori più vecchi. Durante una rotazione del secret, Elido invia anche X-Elido-Signature-Previous, firmato con il vecchio secret, per sette giorni.
Come faccio a non elaborare due volte lo stesso webhook?
Memorizza il valore dell'header X-Webhook-Delivery e salta qualsiasi richiesta il cui valore hai già gestito. Identifica una consegna verso un endpoint e resta invariato tra i retry automatici e i reinvii manuali, quindi un indice univoco su di esso è sufficiente.
Chi può creare o eliminare webhook in un workspace?
Gli amministratori e i proprietari del workspace. Elencare gli endpoint e leggere il log di consegna richiede accesso in visualizzazione; creare, modificare, eliminare, ruotare un secret o reinviare una consegna richiede il permesso workspace.edit. Le chiavi API sono limitate al ruolo scelto al momento della creazione della chiave.
Prova Elido
Incolla un URL, ottieni un link breve
Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.
Gratis, nessuna registrazione richiesta · 2 al giorno