S’exécute surWrit CloudAuto-hébergé
Sur cette page
Référence & guide
Automatisations & webhooks
Une automatisation relie un événement à une action : un changement détecté, un webhook entrant, une planification ou un événement d’exécution déclenche une règle, ses conditions sont vérifiées, et ses actions s’exécutent. Les webhooks transportent les événements en entrée et les résultats en sortie — signés dans les deux sens.
Déclencheurs, conditions, actions
Une automatisation se compose de blocs : un déclencheur (l’événement qui la fait
partir), des conditions optionnelles et une ou plusieurs actions.
Les déclencheurs partent sur ces types d’événements — change_detected est le défaut :
| Type d’événement | Part quand |
|---|---|
change_detected | Une vérification de surveillant trouve un changement réel face à sa référence. |
webhook_received | Un système externe appelle votre hook entrant ou une porte custom_path. |
ai_session_started / ai_session_completed | Une AI session démarre ou se règle. |
workflow_started / workflow_completed | Une exécution de workflow démarre ou se règle. |
monitor_down / monitor_stale / monitor_recovered | Un surveillant cesse de répondre, cesse de rapporter, ou revient. |
crawl_started / crawl_completed / crawl_failed | Un crawl démarre, se termine ou échoue. |
scheduled | Un bloc de planification à la racine de l’automatisation part à l’heure. |
Les actions sont notification, ai_session, workflow,
crawl ou create_persona — plus return_data en forme de bloc
pour répondre à un appelant synchrone. Quand plusieurs règles correspondent, la priorité est
l’ordre d’exécution. Les conditions utilisent les mêmes onze opérateurs, le même contexte
de template et les mêmes filtres que dans surveillants.
Writ s’exécute sur vos propres comptes, avec vos propres identifiants et données, sur les sites que vous êtes autorisé à utiliser.
Webhooks entrants (signés)
Chaque hook entrant possède un secret de signature, attribué à sa création — il ne peut pas être
effacé, et les appels non signés sont rejetés. La signature est un HMAC-SHA256, encodé en
hexadécimal, sur "{timestamp}." + corps brut :
POST /api/webhooks/hook/{token}
Content-Type: application/json
X-Writ-Timestamp: 1718980000
X-Writ-Signature: sha256=<hex>
{ "sku": "SKU-123" } X-Writ-Timestampest obligatoire ; absent, invalide ou plus vieux que 300 secondes, la réponse est401.- La même signature revue sous 300 secondes est rejetée avec
403— un appel capturé ne peut pas être rejoué. - Un en-tête
X-Hub-Signature-256à la GitHub est accepté en alternative àX-Writ-Signature. - Chaque token de hook est limité à 30 appels par 60 secondes ; au-delà, la réponse est
429.
Portes custom_path
Un déclencheur webhook peut aussi revendiquer un custom_path — un chemin lisible
d’au plus 100 caractères, unique dans votre espace de travail — servi à une URL stable et
authentifié par une clé API plutôt que par une signature par appel :
POST /api/v1/webhooks/{custom_path}?wait=true&timeout=120
Authorization: Bearer wt_xxxxxxxxxxxx
Content-Type: application/json
{ "sku": "SKU-123" } Authorization: Beareravec une clé API est obligatoire — sans clé valide, la réponse est401. Le chemin se résout dans l’espace de travail de la clé appelante.- L’
actionde la porte estrun_workflow(défaut) oucheck_target. - Une porte
run_workflowcompte dans le quota d’endpoints publiés de votre plan. - Appels synchrones : réglez
wait_for_resultsur le déclencheur (false par défaut) avecwait_timeoutde 10 à 300 secondes (120 par défaut) — ou surchargez par appel avec?wait=et?timeout=.
Livraisons sortantes
Le canal de notification webhook poste les résultats vers votre endpoint, signés pour que vous puissiez les vérifier. Les livraisons se comportent de façon prévisible :
POSTouPUTuniquement, avecUser-Agent: Writ-Webhook/1.0etX-Writ-Timestampsur chaque requête.- Vérifiez
X-Writ-Signature-V1: il couvre« {timestamp}. » + le corps brut, la même matière qu’un appel entrant, donc une seule recette sert les deux directions et une livraison capturée expire avec son horodatage. X-Writ-Signaturevoyage à côté et couvre le corps JSON uniquement. Il existe pour que les handlers écrits avant V1 continuent de fonctionner — ne l’utilisez pas dans du code neuf.- Les redirections ne sont jamais suivies, et les livraisons vers des destinations de réseau privé sont refusées — une destination refusée n’est pas retentée.
- Jusqu’à 3 tentatives, avec un délai de 30 secondes chacune et un backoff exponentiel plafonné à 30 secondes.
Et ensuite
- Surveillants : le pipeline de déclenchement, les opérateurs de condition et les filtres de template.
- Workflows : ce qu’exécute une action run_workflow.
- Managed endpoints : le quota d’endpoints publiés que partagent les portes custom_path.
Deux directions, deux signatures
Les webhooks circulent dans les deux sens : un système externe peut lancer une automatisation Writ, et Writ peut poster vers votre endpoint. Les deux directions sont signées en HMAC — mais elles ne signent pas la même matière, vérifiez donc chacune correctement.
POST vers votre URL de hook avec un en-tête d’horodatage et une signature sur « {timestamp}. » + le corps brut. L’horodatage doit être frais (moins de 300 secondes) et une signature répétée est rejetée comme rejeu.
Writ livre un payload JSON avec une signature sur le corps uniquement. L’horodatage voyage en en-tête à côté de la signature, pas dans le MAC.
Signer un appel de déclenchement entrant
Calculez un HMAC-SHA256 avec le secret du hook sur "{timestamp}." + body, encodez-le en hexadécimal et envoyez les deux en-têtes. La signature est obligatoire — les appels non signés sont rejetés, et le secret est attribué avec le hook sans pouvoir être désactivé. Un en-tête X-Hub-Signature-256 à la GitHub est accepté en alternative.
send.py
import hashlib, hmac, json, os, time
import requests
secret = os.environ["WEBHOOK_SECRET"] # shown when the inbound hook is created
body = json.dumps({"sku": "SKU-123"})
ts = str(int(time.time()))
sig = hmac.new(secret.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
requests.post(
"https://api.usewrit.app/api/webhooks/hook/{token}",
data=body,
headers={
"Content-Type": "application/json",
"X-Writ-Timestamp": ts,
"X-Writ-Signature": f"sha256={sig}",
},
timeout=30,
) send.ts
import { createHmac } from "node:crypto";
const secret = process.env.WEBHOOK_SECRET!; // shown when the inbound hook is created
const body = JSON.stringify({ sku: "SKU-123" });
const ts = Math.floor(Date.now() / 1000).toString();
const sig = createHmac("sha256", secret).update(`${ts}.${body}`).digest("hex");
await fetch("https://api.usewrit.app/api/webhooks/hook/{token}", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Writ-Timestamp": ts,
"X-Writ-Signature": `sha256=${sig}`,
},
body,
}); send.sh
BODY='{"sku": "SKU-123"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" \
| openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //')
curl -X POST https://api.usewrit.app/api/webhooks/hook/$HOOK_TOKEN \
-H "Content-Type: application/json" \
-H "X-Writ-Timestamp: $TS" \
-H "X-Writ-Signature: sha256=$SIG" \
-d "$BODY" Vérifier une livraison sortante
Prenez X-Writ-Signature-V1, retirez le préfixe sha256=, recalculez un HMAC-SHA256 sur « {timestamp}. » + le corps brut avec le secret de votre endpoint, et comparez en temps constant. L’ancien X-Writ-Signature couvre le corps seul et reste envoyé pour les handlers écrits avant V1 — le code neuf doit vérifier V1.
verify.py
import hashlib, hmac, os
def verify(raw_body: bytes, signature: str) -> bool:
secret = os.environ["WRIT_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
# Constant-time compare - never use ==
return hmac.compare_digest(expected, signature) verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: Buffer, signature: string): boolean {
const expected = createHmac("sha256", process.env.WRIT_WEBHOOK_SECRET!)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signature, "utf8");
return a.length === b.length && timingSafeEqual(a, b);
} verify.go
package writ
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"os"
)
func Verify(rawBody []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(os.Getenv("WRIT_WEBHOOK_SECRET")))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
} verify.rs
use hmac::{Hmac, Mac};
use sha2::Sha256;
pub fn verify(raw_body: &[u8], signature: &str) -> bool {
let secret = std::env::var("WRIT_WEBHOOK_SECRET").unwrap_or_default();
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect("key");
mac.update(raw_body);
let expected = hex::encode(mac.finalize().into_bytes());
// Constant-time compare
expected.len() == signature.len()
&& expected
.bytes()
.zip(signature.bytes())
.fold(0u8, |acc, (a, b)| acc | (a ^ b))
== 0
} Vérifiez toujours avant d’agir. Utilisez le corps brut, non analysé — l’analyser puis le re-sérialiser change les octets et casse la signature. Contrôlez la fraîcheur de X-Writ-Timestamp et ignorez les payloads déjà traités.
À quoi ressemble une livraison
Une livraison change_detected porte l’événement, un horodatage, le target, le sélecteur qui a changé, et le contenu avant/après avec leurs empreintes. Les livraisons partent en POST ou PUT, avec User-Agent Writ-Webhook/1.0, et les redirections ne sont jamais suivies.
POST /your/webhook/handler HTTP/1.1
Content-Type: application/json
User-Agent: Writ-Webhook/1.0
X-Writ-Timestamp: 1718980000
X-Writ-Signature-V1: sha256=6b3a9c…
X-Writ-Signature: sha256=9f86d0…
{
"event": "change_detected",
"timestamp": "2026-08-03T14:02:11Z",
"target": { "id": 42, "url": "https://example.com/pricing", "name": "Pricing page" },
"selector": { "css": ".price", "name": "price" },
"change": {
"content_before": "$129",
"content_after": "$119",
"content_hash": "…",
"previous_hash": "…"
}
} Ce qui déclenche une automatisation
Un système externe poste vers votre URL de hook signée — ou vers une porte custom_path authentifiée par Bearer.
Une vérification de moniteur trouve un changement réel face à sa référence et le pipeline de déclenchement distribue l’automatisation.
Les événements de cycle de vie des workflows, sessions IA et crawls — démarré, terminé, échoué — et les transitions d’état des moniteurs.
Voir le modèle complet des déclencheurs et actions dans automations et le motif watch-and-act dans moniteurs.