Les webhooks de raccourcisseur d'URL d'Elido publient une enveloppe JSON signée vers votre point d'entrée HTTPS chaque fois que quelque chose change dans un espace de travail : un lien est créé, modifié, supprimé, expire ou atteint son plafond de clics, un membre est invité, un domaine se vérifie. Chaque requête porte un en-tête X-Webhook-Signature: v1=<hex>, qui est un HMAC-SHA256 sur {timestamp}.{raw_body}, et une livraison échouée obtient trois tentatives en environ vingt minutes.
Ce qu'il n'envoie pas aujourd'hui, c'est un webhook de clic sur lien. Les clics sortent via l'API d'analyse et les transmetteurs d'événements à la place, et je montrerai où à la fin. Cet article est la moitié sortante de la surface API, le démarrage rapide API + SDK du raccourcisseur d'URL couvre la moitié entrante, et liens intelligents expliqués est le pilier fonctionnalités d'où proviennent les événements de lien.
Quels événements de lien déclenchent un webhook aujourd'hui
Chaque événement ci-dessous atteint un point d'entrée de webhook qui s'y est abonné par nom. Le formulaire de nouveau point d'entrée du tableau de bord propose des cases à cocher pour les huit plus courants ; l'API accepte n'importe quel nom de la liste.
| Événement | Se déclenche quand | Case à cocher tableau de bord |
|---|---|---|
link.created | Un lien est créé, un par un ou en import en masse | oui |
link.updated | Destination, paramètres ou statut change, modifications en masse, restaurations | oui |
link.deleted | Un lien est supprimé, seul ou en masse | oui |
link.expired | Un lien dépasse sa date d'expiration | API uniquement |
link.cap_reached | Un lien atteint son nombre maximal de clics | API uniquement |
link.broken | La vérification de lien cassé voit la destination échouer | API uniquement |
workspace.created, workspace.updated | Un espace de travail est provisionné ou ses paramètres changent | oui |
member.invited, member.removed | Un membre est ajouté (directement, par SCIM ou une invitation acceptée) ou supprimé | oui |
member.role_changed | Le rôle d'un membre change | API uniquement |
invitation.created, invitation.accepted | Une invitation est envoyée ou acceptée | API uniquement |
domain.verified, domain.ssl_failed | Un domaine personnalisé passe les vérifications DNS, ou y échoue toujours après 24 heures | API uniquement |
audit.event | Toute entrée du journal d'audit | oui |
Les liens chiffrés ajoutent deux de plus, link.encrypted_created et link.encryption_rotated. Si vous préférez ne pas maintenir la liste à jour à la main, créez un point d'entrée siem : il reçoit chaque événement de l'espace de travail, entrées d'audit incluses, sans aucun filtre d'abonnement.
Sur la feuille de route, pas encore en production : click.created (un flux de clics échantillonné), des événements de facturation comme billing.subscription_upgraded, et des filtres par point d'entrée comme « seulement les liens dans le dossier X ». Ne vous abonnez pas à ces noms aujourd'hui ; rien ne les publie.
Créer un point d'entrée de webhook avec l'API
Un point d'entrée appartient à un espace de travail. Vous le créez avec un POST vers /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 réponse est 201 avec le point d'entrée et, pour les types event et siem, un secret à usage unique :
{
"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 génère le secret ; vous n'en envoyez pas. Copiez-le maintenant, car aucun appel ultérieur ne le renvoie. kind prend cinq valeurs. event et siem sont les livraisons JSON signées que cet article couvre. discord, telegram et sentry remodèlent les mêmes événements en message de discussion ou en événement Sentry, s'authentifient via l'URL ou un jeton de bot chiffré, et ne portent aucun en-tête HMAC.
Les permissions ont changé ce mois-ci. Lire les points d'entrée et le journal de livraison nécessite workspace.view. Créer, modifier, supprimer, faire tourner un secret ou renvoyer une livraison nécessite workspace.edit, ce qui signifie un administrateur ou propriétaire. Une clé API fonctionne dans l'espace de travail pour lequel elle a été émise et jamais au-dessus du rôle choisi à sa création, donc une clé de niveau viewer obtient un 403 sur le POST ci-dessus. Référence complète : la documentation webhooks.
L'enveloppe de charge utile du webhook
Chaque livraison signée a la même enveloppe à quatre champs. data contient l'enregistrement qui a changé, donc pour les événements de lien c'est la ligne du lien :
{
"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"
}
Cet échantillon est tronqué ; le vrai data porte chaque colonne du lien, y compris les règles de ciblage, le dossier, la campagne et les champs de scan. Il n'y a ni ID d'événement ni short_url dans le corps, donc construisez l'URL courte depuis votre domaine et le slug si vous en avez besoin. Les événements programmés envoient un objet plus petit à la place : link.expired a link_id, slug et destination_url, et link.cap_reached ajoute cap et clicks.
Une correction que vous devriez connaître si vous avez journalisé des charges utiles avant cette semaine : les champs secrets sont désormais retirés avant qu'une charge utile ne quitte Elido. Le password_hash d'un lien protégé par mot de passe et le token d'une invitation apparaissaient auparavant dans data ; ils ne le font plus, ni dans les nouvelles livraisons ni dans le journal de livraison. Si vous avez stocké d'anciennes charges utiles, ces données méritent d'être purgées.
Vérifier l'en-tête X-Webhook-Signature
Chaque requête signée porte ces en-têtes :
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
Les deux en-têtes de signature portent la même valeur. Elido calcule HMAC-SHA256, tel que défini dans la RFC 2104, avec la chaîne whsec_... entière comme clé, sur l'horodatage, un point, et les octets bruts du corps. Le condensé hexadécimal reçoit un préfixe v1=. Il n'y a pas de champ t= à l'intérieur de l'en-tête ; l'horodatage vit dans son propre en-tête.
En Node, signez les octets bruts, pas un objet re-sérialisé. Express nécessite express.raw({ type: "application/json" }) sur cette route pour cette raison :
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"));
// Pendant une rotation, l'ancien secret signe 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);
});
}
La vérification de longueur compte : le timingSafeEqual de Node lève une exception sur des buffers de tailles différentes plutôt que de renvoyer false. La version Python avec le module 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
Si la vérification échoue toujours, le guide de vérification des signatures de webhook propose une version Go, un nœud de code n8n et les causes habituelles d'une discordance. La fenêtre de cinq minutes est votre vérification, pas la nôtre. Elido appose un horodatage frais à chaque tentative, nouvelles tentatives incluses, donc une nouvelle tentative légitime ne paraît jamais périmée. Sans la fenêtre, quiconque a capturé une requête pourrait la rejouer la semaine suivante et la signature correspondrait toujours.
La rotation se fait via POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret, ou le bouton Rotate sur la page du point d'entrée. Vous obtenez le nouveau secret une fois. Pendant les sept jours suivants, chaque livraison porte aussi X-Elido-Signature-Previous, signé avec l'ancien secret, ce qui explique pourquoi les deux fonctions ci-dessus l'essaient. Déployez le nouveau secret à tout moment dans cette semaine et rien n'échoue.
La politique de nouvelle tentative de webhook
Le worker de livraison récupère les livraisons en attente toutes les quelques secondes, donc un changement de lien vous atteint généralement en quelques secondes. Toute réponse 2xx marque la livraison comme terminée. Un statut non-2xx, une erreur réseau ou aucune réponse dans les 10 secondes compte comme une tentative échouée.
Trois tentatives par livraison, vingt minutes de bout en bout. C'est court volontairement, et honnêtement c'est plus court que ce que je choisirais pour un récepteur derrière un VPN capricieux. Une panne de deux heures de votre côté ne sera pas couverte par les nouvelles tentatives automatiques. Ce qui la couvre, c'est le journal de livraison : GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries liste chaque livraison avec le statut, le code HTTP, la latence, le nombre de tentatives et l'heure de la prochaine nouvelle tentative, et la page du point d'entrée montre les mêmes lignes avec un bouton Réessayer. Retry, ou POST .../deliveries/{delivery_id}/retry, réarme une livraison échouée ou livrée avec un nouveau budget de trois tentatives et renvoie 202. Une livraison encore en attente reçoit 409.
Certains échecs sautent les nouvelles tentatives. Un point d'entrée Telegram sans son chat_id, ou un DSN Sentry malformé, est marqué échoué immédiatement, parce que répéter la requête ne corrigera pas la configuration. Pour mettre un point d'entrée hors ligne sans le supprimer, envoyez PUT avec "is_active": false ; les points d'entrée en pause ne reçoivent plus de nouvelles livraisons.
Si votre gestionnaire fait un travail lourd, renvoyez 200 d'abord et mettez la tâche en file d'attente. Le délai de dix secondes est là où un gestionnaire lent-mais-réussi se transforme en doublon, ce qui nous amène à la déduplication.
Vous planifiez un récepteur pour votre équipe ? La page fonctionnalité webhooks montre le côté tableau de bord de tout cela.
Idempotence et ordonnancement des webhooks
Elido livre au moins une fois. La section nouvelle tentative a montré une façon dont un doublon se produit : votre gestionnaire valide le travail, puis manque la fenêtre de dix secondes, et Elido l'envoie à nouveau. Un Retry manuel renvoie volontairement.
Les deux cas gardent la même valeur X-Webhook-Delivery, parce que c'est l'ID d'une livraison à un point d'entrée, pas d'une tentative. Indexez dessus :
CREATE TABLE elido_webhook_seen (
delivery_id BIGINT PRIMARY KEY,
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- dans le gestionnaire, dans la même transaction que votre travail :
INSERT INTO elido_webhook_seen (delivery_id) VALUES ($1)
ON CONFLICT (delivery_id) DO NOTHING
RETURNING delivery_id;
-- aucune ligne en retour signifie que vous avez déjà traité cette livraison
Deux points d'entrée abonnés au même événement obtiennent deux ID de livraison différents, donc dédupliquez par point d'entrée. Dans de rares redémarrages de notre côté, un événement peut être mis en file d'attente deux fois comme livraisons séparées ; si une double écriture ferait mal, ajoutez une seconde garde sur type plus data.id plus data.updated_at.
Il n'y a aucune garantie d'ordonnancement. Les livraisons partent dans l'ordre d'échéance, mais une nouvelle tentative d'un événement précoce peut atterrir après un événement plus tardif. Comparez data.updated_at à ce que vous avez stocké avant d'écraser un lien, et ne vous appuyez pas sur l'horodatage timestamp de l'enveloppe pour l'ordonnancement : il n'a qu'une précision à la seconde.
Où vivent les données de clic à la place d'un webhook de clic
C'est la partie que l'ancienne version de cet article a mal comprise. Il n'y a pas de webhook click aujourd'hui, et click.created est prévu, pas livré. Le chemin de redirection est gardé libre de tout travail synchrone, ce que l'article ingestion de clics fire-and-forget explique, et les clics vont vers le stockage d'analyse plutôt que dans la file d'attente de webhook.
Pour les données au niveau du clic maintenant, vous avez deux voies :
- Transmetteurs d'événements. Chaque clic sur lien court devient un événement côté serveur dans l'outil que vous utilisez déjà : événements de clic de lien Mixpanel, événements de clic Klaviyo sur les profils, ou métriques de redirection Datadog pour les tableaux de bord opérationnels.
- L'API d'analyse.
GET /v1/analytics/workspaces/{workspace_id}/clicks/recentrenvoie les clics récents, etclicks.csvles exporte, donc une tâche planifiée peut extraire les lignes dont elle a besoin.
Lequel convient dépend de la latence et de l'endroit où atterrissent les données ; webhooks contre sondage pour le suivi des clics passe en revue ce compromis. Et si le flux échantillonné click.created est lancé, cette page le dira en premier.
Lisez le pilier : liens intelligents expliqués.
À lire aussi sur le blog
- Webhooks contre sondage pour le suivi des clics - quand pousser et quand tirer.
- Démarrage rapide API + SDK du raccourcisseur d'URL - la surface API entrante.
- Ingestion de clics fire-and-forget - pourquoi les clics n'attendent jamais d'appels sortants.
- Événements de clic de lien Mixpanel - données de clic comme événements côté serveur.
- Métriques de redirection de lien Datadog - santé de redirection sur un tableau de bord opérationnel.
- Vérifier les signatures de webhook - vérifications HMAC en Node, Python, Go et n8n, avec débogage d'une discordance.
Questions fréquentes
Elido envoie-t-il un webhook pour chaque clic sur un lien ?
Pas aujourd'hui. Les webhooks couvrent les changements d'espace de travail comme link.created, link.updated, link.expired et member.invited. Un événement click.created est sur la feuille de route comme flux échantillonné. Pour les données au niveau du clic dès maintenant, utilisez l'API d'analyse ou un transmetteur d'événements comme Mixpanel, Klaviyo ou Datadog.
Comment vérifier une signature de webhook Elido ?
Calculez HMAC-SHA256 sur la valeur X-Webhook-Timestamp, un point, et le corps de requête brut, keyé avec votre secret whsec_. Encodez-le en hexadécimal, préfixez v1= et comparez-le en temps constant avec l'en-tête X-Webhook-Signature. Rejetez les horodatages de plus de cinq minutes.
Combien de fois Elido retente-t-il un webhook échoué ?
Chaque livraison obtient trois tentatives : une immédiate, une cinq minutes après un échec, et une quinze minutes après cela. Tout statut non-2xx, erreur réseau ou réponse plus lente que dix secondes compte comme un échec. Après la troisième, la livraison est marquée échouée jusqu'à ce que vous appuyiez sur Réessayer.
Quelle est la différence entre X-Webhook-Signature et X-Elido-Signature ?
Rien à part le nom. Les deux en-têtes portent la même signature v1=, et X-Webhook-Signature reste pour les récepteurs plus anciens. Pendant une rotation de secret, Elido envoie aussi X-Elido-Signature-Previous, signé avec l'ancien secret, pendant sept jours.
Comment arrêter de traiter le même webhook deux fois ?
Stockez la valeur de l'en-tête X-Webhook-Delivery et ignorez toute requête dont vous avez déjà traité la valeur. Elle identifie une livraison à un point d'entrée et reste la même à travers les nouvelles tentatives automatiques et les renvois manuels, donc un index unique dessus suffit.
Qui peut créer ou supprimer des webhooks dans un espace de travail ?
Les administrateurs et propriétaires d'espace de travail. Lister les points d'entrée et lire le journal de livraison nécessite un accès en vue ; créer, modifier, supprimer, faire tourner un secret ou renvoyer une livraison nécessite la permission workspace.edit. Les clés API sont plafonnées au rôle choisi à la création de la clé.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Gratuit, sans inscription · 2 par jour