Se ejecuta enWrit CloudAutoalojado
En esta página
Referencia y guía
Automatizaciones y webhooks
Una automatización conecta un evento con una acción: un cambio detectado, un webhook entrante, una programación o un evento de ejecución dispara una regla, sus condiciones se comprueban y sus acciones se ejecutan. Los webhooks llevan eventos hacia dentro y resultados hacia fuera — firmados en ambos sentidos.
Triggers, condiciones, acciones
Una automatización se compone de bloques: un trigger (el evento que la dispara),
condiciones opcionales y una o varias acciones. Los triggers
disparan con estos tipos de evento — change_detected es el predeterminado:
| Tipo de evento | Dispara cuando |
|---|---|
change_detected | Una comprobación de monitor encuentra un cambio real frente a su referencia. |
webhook_received | Un sistema externo llama a tu hook entrante o a una puerta custom_path. |
ai_session_started / ai_session_completed | Una AI session empieza o se resuelve. |
workflow_started / workflow_completed | Una ejecución de workflow empieza o se resuelve. |
monitor_down / monitor_stale / monitor_recovered | Un monitor deja de responder, deja de informar, o vuelve. |
crawl_started / crawl_completed / crawl_failed | Un crawl empieza, termina o falla. |
scheduled | Un bloque de programación en la raíz de la automatización dispara a su hora. |
Las acciones son notification, ai_session, workflow,
crawl o create_persona — más return_data en forma de bloque
para responder a un llamador síncrono. Cuando varias reglas coinciden, la prioridad es el
orden de ejecución. Las condiciones usan los mismos once operadores, el mismo contexto de
plantilla y los mismos filtros documentados en monitores.
Writ se ejecuta en tus propias cuentas, con tus propias credenciales y datos, en los sitios que estás autorizado a usar.
Webhooks entrantes (firmados)
Cada hook entrante tiene un secreto de firma, asignado al crearlo — no puede borrarse, y las
llamadas sin firma se rechazan. La firma es un HMAC-SHA256, en hexadecimal, sobre
"{timestamp}." + cuerpo en bruto:
POST /api/webhooks/hook/{token}
Content-Type: application/json
X-Writ-Timestamp: 1718980000
X-Writ-Signature: sha256=<hex>
{ "sku": "SKU-123" } X-Writ-Timestampes obligatorio; ausente, inválido o con más de 300 segundos, la respuesta es401.- La misma firma vista de nuevo dentro de 300 segundos se rechaza con
403— una llamada capturada no puede repetirse. - Se acepta un encabezado
X-Hub-Signature-256al estilo GitHub como alternativa aX-Writ-Signature. - Cada token de hook está limitado a 30 llamadas por 60 segundos; más allá, la respuesta es
429.
Puertas custom_path
Un trigger de webhook también puede reclamar un custom_path — una ruta legible de hasta
100 caracteres, única en tu espacio de trabajo — servida en una URL estable y autenticada con una
clave API en lugar de una firma por llamada:
POST /api/v1/webhooks/{custom_path}?wait=true&timeout=120
Authorization: Bearer wt_xxxxxxxxxxxx
Content-Type: application/json
{ "sku": "SKU-123" } Authorization: Bearercon una clave API es obligatorio — sin clave válida, la respuesta es401. La ruta se resuelve dentro del espacio de trabajo de la clave llamante.- La
actionde la puerta esrun_workflow(predeterminada) ocheck_target. - Una puerta
run_workflowcuenta contra el cupo de endpoints publicados de tu plan. - Llamadas síncronas: fija
wait_for_resulten el trigger (false por defecto) conwait_timeoutde 10 a 300 segundos (120 por defecto) — o sobreescribe por llamada con?wait=y?timeout=.
Entregas salientes
El canal de notificación webhook publica los resultados en tu endpoint, firmados para que puedas verificarlos. Las entregas se comportan de forma previsible:
- Solo
POSToPUT, conUser-Agent: Writ-Webhook/1.0yX-Writ-Timestampen cada petición. - Verifica
X-Writ-Signature-V1: cubre"{timestamp}." + el cuerpo en bruto, el mismo material que firma una llamada entrante, así que una sola receta sirve para ambas direcciones y una entrega capturada caduca con su marca de tiempo. X-Writ-Signatureviaja al lado y cubre solo el cuerpo JSON. Existe para que los handlers escritos antes de V1 sigan funcionando — no lo uses en código nuevo.- Las redirecciones nunca se siguen, y las entregas a destinos de red privada se rechazan — un destino rechazado no se reintenta.
- Hasta 3 intentos, con 30 segundos de plazo cada uno y backoff exponencial con tope de 30 segundos.
Qué sigue
- Monitores: el pipeline de triggers, los operadores de condición y los filtros de plantilla.
- Workflows: qué ejecuta una acción run_workflow.
- Managed endpoints: el cupo de endpoints publicados que comparten las puertas custom_path.
Dos direcciones, dos firmas
Los webhooks fluyen en ambos sentidos: un sistema externo puede iniciar una automatización de Writ, y Writ puede hacer POST a tu endpoint. Ambas direcciones van firmadas con HMAC — pero firman material distinto, así que verifica cada una como corresponde.
POST a tu URL de hook con un encabezado de marca de tiempo y una firma sobre "{timestamp}." + el cuerpo en bruto. La marca de tiempo debe ser fresca (dentro de 300 segundos) y una firma repetida se rechaza como repetición.
Writ entrega un payload JSON con una firma sobre el cuerpo únicamente. La marca de tiempo viaja como encabezado junto a la firma, no dentro del MAC.
Firmar una llamada de trigger entrante
Calcula un HMAC-SHA256 con el secreto del hook sobre "{timestamp}." + body, codifícalo en hexadecimal y envía ambos encabezados. La firma es obligatoria — las llamadas sin firma se rechazan, y el secreto se asigna con el hook y no puede desactivarse. Se acepta como alternativa un encabezado X-Hub-Signature-256 al estilo GitHub.
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" Verificar una entrega saliente
Toma X-Writ-Signature-V1, quita el prefijo sha256=, recalcula un HMAC-SHA256 sobre "{timestamp}." + el cuerpo en bruto con el secreto de tu endpoint, y compara en tiempo constante. El antiguo X-Writ-Signature cubre solo el cuerpo y se sigue enviando para handlers escritos antes de V1 — el código nuevo debe verificar 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
} Verifica siempre antes de actuar. Usa el cuerpo en bruto, sin parsear — parsearlo y volver a serializarlo cambia los bytes y rompe la firma. Comprueba la frescura de X-Writ-Timestamp y descarta payloads ya procesados.
Cómo es una entrega
Una entrega change_detected lleva el evento, una marca de tiempo, el target, el selector que cambió y el contenido antes/después con sus huellas. Las entregas salen como POST o PUT, con User-Agent Writ-Webhook/1.0, y las redirecciones nunca se siguen.
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": "…"
}
} Qué dispara una automatización
Un sistema externo hace POST a tu URL de hook firmada — o a una puerta custom_path autenticada con Bearer.
Una comprobación de monitor encuentra un cambio real frente a su referencia y el pipeline de triggers despacha la automatización.
Eventos de ciclo de vida de workflows, AI sessions y crawls — iniciado, completado, fallido — y transiciones de salud de los monitores.
Consulta el modelo completo de triggers y acciones en automations y el patrón watch-and-act en monitores.