Les webhooks transforment n'importe quel système amont en éditeur. Signez une charge utile, envoyez-la en POST, et le moteur la mappe vers du contenu et reconstruit — aucun échange de clé dans le chemin de la requête, car la signature fait office d'authentification.
Le point de terminaison
POST https://your-host/webhooks/{integration} {integration} est un slug que vous choisissez lors de la création de l'intégration — pas le nom d'un mapper. Chacun possède son propre slug, son propre secret, et un seul mapper associé, ce qui permet à un site d'en avoir plusieurs : newsroom, docs-sync, partner-feed. Les slugs sont composés de lettres minuscules, de chiffres et de tirets.
php bin/fastsite webhook:create newsroom --site=blog --mapper=autoseo Cette commande affiche l'URL complète et le secret de signature une seule fois. Vous pouvez faire de même depuis le tableau de bord, l'API REST, ou l'outil MCP create_webhook — les webhooks sont rattachés au site, donc tout membre d'un site peut les gérer sans être administrateur.
Signer une requête
Le corps brut est lu tel quel et vérifié avant toute autre opération. Deux en-têtes sont requis :
X-Fastsite-Signature: t=<unix>,v1=<hmac_sha256("<t>.<body>", secret)>— la chaîne signée est l'horodatage, un point littéral, et les octets exacts que vous envoyez. Comparée en temps constant, dans une fenêtre de ±300 secondes.X-Delivery-Id— unique par livraison. Il constitue la protection contre la rediffusion : un identifiant répété est accepté avec un200et silencieusement ignoré plutôt que traité deux fois.
Signez les octets que vous transmettez réellement. La re-sérialisation du JSON entre la signature et l'envoi est la cause habituelle d'une signature qui ne peut pas être vérifiée.
Un exemple concret
# body.json est l'ensemble exact des octets que vous signez et envoyez
SECRET="whsec_…"
BODY=$(cat body.json)
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | \
openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST https://your-host/webhooks/newsroom \
-H "X-Fastsite-Signature: t=$TS,v1=$SIG" \
-H "X-Delivery-Id: $(uuidgen)" \
-H "Content-Type: application/json" \
--data-binary @body.json Ce qui est renvoyé
| Code | Signification |
|---|---|
| 202 | Accepté. Enregistré et mis en file d'attente. |
| 200 | Identifiant de livraison dupliqué — déjà reçu, aucune action effectuée. |
| 400 | X-Delivery-Id manquant. |
| 401 | Signature manquante ou invalide. |
| 404 | Intégration introuvable ou désactivée. |
Le point de terminaison répond en quelques millisecondes car il se contente d'enregistrer et de mettre en file d'attente. Le mapping, la construction et la publication ont lieu ensuite dans la file, avec des nouvelles tentatives et un délai exponentiel.
Les webhooks publient ; l'API ne le fait pas
C'est la seule vraie différence entre les deux chemins d'ingestion. La création de contenu via REST ou MCP le stocke et attend que vous le publiiez. Une livraison par webhook exécute son mapper puis enchaîne automatiquement une construction et une publication — un système amont qui pousse un article s'attend à ce qu'il soit en ligne, pas en attente.
Mappers intégrés
Les mappers sont du code, pas des chaînes de configuration. Chacun prend une charge utile générique et la transforme en action moteur.
| Mapper | Charge utile | Action |
|---|---|---|
autoseo | {id, title, body_html, slug?, excerpt?, featured_image_url?, author?, seo_title?, seo_description?, status?, published_at?} | Ingestion d'article idempotente, indexée sur id. |
create_post | {title, body_html, slug?, excerpt?, status?, author?, source_ref?} | Mappe une charge utile vers un article. |
create_page | Identique à ce qui précède | Mappe une charge utile vers une page. |
import_media | {url, alt?, decorative?} | Récupère un fichier par URL (protégé contre les SSRF) dans la bibliothèque. |
upsert_component | {handle, type, source_code, css?} | Crée ou met à jour un composant. |
autoseo est le mapper à privilégier pour un pipeline d'articles. Il est idempotent sur le champ id de la charge utile, de sorte qu'une nouvelle livraison met à jour l'article existant au lieu d'en créer un second. Il gère également les images : l'image mise en avant et chaque <img> externe dans le corps sont récupérées dans votre bibliothèque multimédia et réécrites en références locales, avec un texte alternatif dérivé du titre lorsque la source n'en fournit pas. Si une image ne peut pas être récupérée, elle est supprimée et l'article est quand même publié — une panne de CDN en amont ne devrait pas vous faire perdre l'article.
Pour les mappers de contenu, status vaut par défaut published. Passez source_ref si vous souhaitez qu'une nouvelle livraison mette à jour plutôt que de dupliquer.
En cas de problème
Chaque livraison est enregistrée avec ses en-têtes, son corps et son résultat, ce qui permet d'inspecter un échec plutôt que de le perdre.
php bin/fastsite webhook:list
php bin/fastsite webhook:deliveries --limit=20
php bin/fastsite webhook:replay <delivery-id> Une charge utile que le mapper ne peut pas utiliser — un id manquant, pas de title, un JSON malformé — est marquée skipped et ne fait l'objet d'aucune nouvelle tentative, car réessayer ne résoudra pas le problème. Tout autre échec est marqué failed et retenté avec un délai exponentiel. La rediffusion réexécute une livraison stockée à partir de ses octets enregistrés, ce qui vous permet de corriger un mapper ou un identifiant et de retraiter sans demander à l'expéditeur de réessayer.
Les secrets sont stockés chiffrés et peuvent être de nouveau affichés depuis le tableau de bord pour configurer un expéditeur. Vous préférez récupérer plutôt qu'être notifié ? L'API REST et le serveur MCP couvrent les mêmes opérations.