Webhook de publication
Recevez les articles LinkQuiver en POST JSON signé HMAC sur votre propre endpoint. Le canal de publication pour les sites non-WordPress : Next.js, Symfony, Astro, CMS headless…
Comment ça marche
À chaque date planifiée, LinkQuiver envoie une requête POST application/json à l'URL que vous avez configurée. Le corps contient l'article complet (HTML + Markdown + image de couverture). Vérifiez la signature, répondez 2xx pour accuser réception, puis faites-en ce que vous voulez : publier, stocker, transformer.
Configuration
Dashboard → votre site → onglet Intégrations → section Webhook. Enregistrez votre URL : le secret de signature (64 caractères hexadécimaux) est affiché une seule fois à la création. Stockez-le côté serveur (variable d'environnement, jamais dans du code client). Secret perdu ? Régénérez-le depuis le même écran puis mettez à jour votre receveur.
En-têtes envoyés à chaque appel
X-LQ-Event— type d'événement (ex. article.published)X-LQ-Delivery-Id— identifiant unique de la livraison (= idempotency_key, pour dédupliquer)X-LQ-Timestamp— horodatage ISO 8601 de l'envoi, à inclure dans le calcul HMACX-LQ-Signature— sha256=<hex> : la signature HMAC à vérifier
Compatibilité : chaque requête porte aussi les mêmes valeurs sous les anciens noms X-BLE-* (X-BLE-Signature, X-BLE-Timestamp…). Les receveurs existants continuent de fonctionner ; utilisez X-LQ-* pour toute nouvelle intégration.
Vérifier la signature
Chaque requête est signée HMAC SHA-256 avec votre secret. La chaîne signée est la concaténation du timestamp, d'un point et du corps brut :
signature = "sha256=" + hex( HMAC_SHA256( secret, timestamp + "." + body ) )- Lisez les en-têtes X-LQ-Signature et X-LQ-Timestamp.
- Concaténez timestamp + "." + corps BRUT de la requête (les octets reçus, pas un objet re-sérialisé).
- Calculez le HMAC SHA-256 avec votre secret, encodez en hexadécimal, préfixez de sha256=.
- Comparez avec X-LQ-Signature en temps constant (timingSafeEqual ou équivalent).
- Recommandé : rejetez si l'écart entre maintenant et timestamp dépasse 5 minutes (anti-rejeu).
Signez le corps BRUT reçu, jamais un objet re-sérialisé : JSON.stringify peut réordonner les clés et casser la signature.
Événements possibles
test— envoyé par le bouton « Tester » du dashboardarticle.published— nouvel article publié (objet article)article.link_inserted— article existant mis à jour avec un lien inséré (objet article + link_insertion)resurrection.published— page d'un domaine ressuscité (objet page au lieu de article)
Exemple de payload (article.published)
{
"event": "article.published",
"delivery_id": "9f1c2e7a-4b3d-4f8a-9c10-2b6e5d8a1f33",
"idempotency_key": "9f1c2e7a-4b3d-4f8a-9c10-2b6e5d8a1f33",
"timestamp": "2026-06-26T10:00:00.000Z",
"site": {
"domain": "villaor.fr",
"vdl_website_id": "b2a1c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"article": {
"id": "c7d8e9f0-1234-5678-9abc-def012345678",
"subject": "Location villa avec piscine en Provence",
"title": "Louer une villa avec piscine en Provence : le guide",
"slug": "louer-villa-piscine-provence-guide",
"html": "<h1>Louer une villa…</h1><p>…</p>",
"markdown": "# Louer une villa…\n\n…",
"meta_title": "Villa avec piscine en Provence | Guide",
"meta_description": "Tout pour louer la bonne villa…",
"cover_image_url": "https://…/cover.jpg",
"scheduled_at": "2026-06-26T10:00:00.000Z"
}
}Images : valides 30 jours
Les images (la couverture cover_image_url ET les <img> dans le HTML/Markdown) ne sont garanties que 30 jours : passé ce délai, leurs URLs renvoient une 404. Téléchargez-les et réhébergez-les sur votre propre stockage dès réception.
Réponse attendue et bonnes pratiques
- Répondez 2xx rapidement — toute autre réponse compte comme un échec ; le timeout est de 30 secondes. Traitez en asynchrone si besoin et accusez réception tout de suite.
- Dédupliquez avec delivery_id — une livraison échouée est retentée (jusqu'à 3 tentatives) : le même delivery_id peut donc arriver plusieurs fois.
- Endpoint public obligatoire — les adresses internes (localhost, IP privées) sont refusées. HTTPS fortement recommandé.
- User-Agent — les requêtes portent le User-Agent LinkQuiver-Webhook/1.0, utile si vous filtrez en amont.
Exemple complet : Node.js / Express
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.LQ_WEBHOOK_SECRET; // le secret affiché une seule fois
const app = express();
app.use(express.raw({ type: 'application/json' })); // IMPORTANT : on signe le corps BRUT, pas un objet re-sérialisé
app.post('/api/webhook', (req, res) => {
const signature = req.header('X-LQ-Signature') ?? '';
const timestamp = req.header('X-LQ-Timestamp') ?? '';
const body = req.body.toString('utf8');
const expected =
'sha256=' +
crypto.createHmac('sha256', SECRET)
.update(`${timestamp}.${body}`)
.digest('hex');
const ok =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(401).json({ reason: 'invalid_signature' });
// optionnel : rejeter si |maintenant - timestamp| > 5 min
const payload = JSON.parse(body); // signature valide → traitez le payload
console.log(payload.event, payload.article?.title);
return res.status(200).json({ received: true });
});Prêt à brancher votre site ?
Configurez votre webhook en deux minutes depuis les connexions de votre dashboard, puis validez avec l'événement test.
Configurer dans le dashboard