Livré sans ralentir le clic
Le clic visiteur est servi d'abord. La livraison webhook est journalisée et traitée en arrière-plan.
Bêta développeur
Envoyez les clics, règles de routage servies et événements de liens courts vers vos outils internes, Make, Zapier, un CRM ou un tableau de reporting. Les redirections visiteurs restent prioritaires : les webhooks partent en arrière-plan.
Statut bêta : l'interface, la signature et les retries sont opérationnels. Les événements et champs peuvent encore évoluer avant stabilisation complète ; vérifiez vos intégrations après chaque ajout d'événement.
Le clic visiteur est servi d'abord. La livraison webhook est journalisée et traitée en arrière-plan.
Chaque POST contient une signature HMAC SHA-256 dans X-Linka-Factory-Signature.
Les erreurs réseau et réponses HTTP non 2xx sont retentées automatiquement, avec un journal visible dans Settings.
campaign.click.recordedUn clic a été servi par une campagne.
campaign.routing_rule.matchedUne règle de routage avancé a été appliquée avant la répartition.
short_link.click.recordedUn clic a été servi par un lien court Linka Factory.
{
"id": "evt_campaign_click_recorded_01hq...",
"type": "campaign.click.recorded",
"created_at": "2026-05-01T17:40:00.000Z",
"account_id": "acc_...",
"data": {
"accountId": "acc_...",
"timestamp": "2026-05-01T17:40:00.000Z",
"campaignId": "cmp_...",
"urlId": "url_...",
"servedUrl": "https://example.com/landing-a",
"routingRuleId": "rule_...",
"routingRuleLabel": "France mobile",
"country": "FR",
"deviceType": "mobile",
"browserFamily": "chrome",
"platformFamily": "android",
"sourceSite": "newsletter",
"refererHost": "example.com",
"ipMasked": "203.0.113.0",
"suspicious": false
}
}La signature est calculée sur la chaîne timestamp.point.body brut. Gardez le body exact reçu avant parsing JSON.
import crypto from "node:crypto"
export function verifyLinkaFactoryWebhook({ rawBody, signatureHeader, secret }) {
if (!Buffer.isBuffer(rawBody) && typeof rawBody !== "string") return false
if (typeof signatureHeader !== "string" || typeof secret !== "string" || !secret) return false
const match = /^t=([0-9]{1,12}),v1=([a-fA-F0-9]{64})$/.exec(signatureHeader)
if (!match) return false
const timestamp = match[1]
const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
if (ageSeconds > 300) return false
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + ".")
.update(rawBody)
.digest()
const received = Buffer.from(match[2], "hex")
return received.length === expected.length && crypto.timingSafeEqual(expected, received)
}Cet exemple accepte un écart d'horloge de cinq minutes. Après validation de la signature, dédupliquez l'identifiant id du JSON dans votre base : une contrainte unique et le traitement métier doivent partager la même transaction. Un événement déjà traité doit recevoir une réponse 2xx, sans répéter ses effets. La signature seule ne bloque pas un rejeu dans cette fenêtre.
Livraison
Un statut HTTP 2xx marque la livraison comme envoyée. Les autres statuts, timeouts et erreurs réseau passent en nouvelle tentative.
Après un échec, les délais prévus avant les réessais sont de 5 minutes, 30 minutes, puis 2 heures et encore 2 heures, avec 5 tentatives maximum, envoi initial inclus. L’heure effective dépend du traitement de la file.
Le journal des dernières livraisons affiche le statut, le nombre de tentatives, le code HTTP et un extrait de réponse pour déboguer vite.
Prochaine étape