Documentación
Webhooks salientes: payload y firma
Recibe las alertas e informes de AutoPSEO como JSON firmado en cualquier endpoint HTTPS: las cabeceras de la petición, los campos del cuerpo, los tipos de evento y cómo comprobar la firma.
Un webhook saliente envía las alertas, los informes programados, los mensajes de prueba y los resúmenes de informes de AutoPSEO a un endpoint que tú gestionas, en forma de un POST JSON por mensaje firmado con HMAC-SHA256. Úsalo para alimentar Zapier, Make, n8n, una herramienta de incidencias o tu propio código; no hace falta ninguna cuenta de Slack ni de Teams. ¿Buscas el webhook que entrega artículos a tu sitio? Ese pertenece al motor de contenido: consulta la entrega de contenido.
Añadir un webhook
- En la sección Webhooks salientes, haz clic en Nuevo webhook.
- Ponle un Nombre y pega la URL del endpoint. Debe ser HTTPS público: se rechazan
httpsin cifrar,localhost, los nombres que terminan en.localo.internal, los hosts sin punto y las direcciones IP privadas o de enlace local. - Copia el secreto de firma que aparece después de guardar. Solo se muestra esta vez.
- Haz clic en Enviar prueba para publicar un evento
testy después asocia al webhook informes programados o alertas.
Cómo es una petición de webhook
Cada mensaje es un POST con estas cabeceras:
| Cabecera | Valor |
|---|---|
Content-Type | application/json |
User-Agent | AutoPSEO-Webhook/1.0 |
X-AutoPSEO-Event | El tipo de evento, por ejemplo traffic_drop |
X-AutoPSEO-Signature | sha256= seguido de 64 caracteres hexadecimales en minúscula |
Este es un ejemplo de alerta de caída de tráfico para una propiedad de dominio, de la ejecución de las 06:00 UTC del 12 de octubre de 2026, formateada para facilitar la lectura (en la red viaja como JSON compacto, y los valores de id y site_id son inventados):
{
"id": "9b2e61f04c3a4d7e8f15a0b6c2d39e47",
"event_type": "traffic_drop",
"severity": "high",
"site_id": "4f0c1e9a2b7d4c58",
"site_url": "sc-domain:example.com",
"occurred_at": "2026-10-12T06:00:04.183Z",
"title": "Traffic drop: example.com",
"text": "Clicks 1,500 vs 2,000 in the previous window (-25.0%).",
"url": "https://app.autopseo.com/property/sc-domain%3Aexample.com",
"payload": {
"fields": [
{ "label": "Metric", "value": "clicks" },
{ "label": "Current", "value": "1,500" },
{ "label": "Baseline", "value": "2,000" },
{ "label": "Window", "value": "7 days (2026-10-05 → 2026-10-11 vs 2026-09-28 → 2026-10-04)" },
{ "label": "Change", "value": "-25.0%" }
],
"table": null,
"detector": "traffic_drop",
"window": 7,
"range": { "start": "2026-10-05", "end": "2026-10-11", "compStart": "2026-09-28", "compEnd": "2026-10-04" },
"result": {
"fired": true,
"metric": "clicks",
"value": 1500,
"baseline": 2000,
"change": -0.25,
"severity": "high",
"summary": "Clicks 1,500 vs 2,000 in the previous window (-25.0%)."
}
},
"source": "autopseo"
}
Los campos de primer nivel son los mismos para todos los eventos:
| Campo | Qué contiene |
|---|---|
id | Una cadena hexadecimal aleatoria de 32 caracteres, nueva en cada petición |
event_type | Qué ha ocurrido (consulta la sección siguiente) |
severity | critical, high, medium o low para las alertas; info para informes, resúmenes y pruebas |
site_id | El id de AutoPSEO de la propiedad, o null en las pruebas y en la propiedad de demostración |
site_url | La propiedad de Search Console, como sc-domain:example.com o https://www.example.com/, o null en las pruebas |
occurred_at | Cuándo se construyó el mensaje, como marca de tiempo ISO 8601 en UTC |
title, text | El titular y la frase de resumen que muestran Slack y Teams |
url | El informe en AutoPSEO, o null en las pruebas |
payload.fields | Los valores etiquetados que Slack muestra como campos y Teams como datos |
payload.table | columns y rows para informes y resúmenes; en el resto de casos, null |
source | Siempre autopseo |
Los ratios son fracciones: un change de -0.25 es una caída del 25 %, y un ctr de 0.034 es un 3,4 %.
Tipos de evento
event_type | Se envía cuando | Claves adicionales en payload |
|---|---|---|
traffic_drop, traffic_spike, position_drop, ctr_drop, new_lost_rankings, anomaly, index_flip, rank_drop, audit_regression | Se activa una regla de alerta, según la programación o mediante Probar regla | detector, window, range (las fechas, o null para index_flip, rank_drop y audit_regression), result |
scheduled_report | Se envía un informe programado, según la programación o mediante Enviar ahora | reportType (summary, queries, pages o rankings), range (fechas más un label), totals, previous |
report_summary | Alguien usa Enviar a Slack en la página de un informe | Ninguna: las cifras están en fields y table |
test | Alguien hace clic en Enviar prueba en el webhook | sample, siempre true |
result repite lo que midió el detector: fired, metric, value, baseline, change (null cuando no hay nada con lo que comparar), severity y summary, además de z, la puntuación z modificada, en anomaly. totals y previous contienen clicks, impressions, ctr y position de las dos ventanas de un informe. Lo que mide cada detector se describe en Alertas.
Cómo verificar la firma
La firma es un HMAC-SHA256 de los bytes exactos que envió AutoPSEO, calculado con el secreto de firma del webhook y escrito como sha256= más hexadecimal en minúscula. Para comprobarla:
- Lee el cuerpo bruto de la petición antes de analizar el JSON. Si lo analizas y lo vuelves a serializar, los bytes pueden cambiar.
- Calcula el HMAC-SHA256 sobre esos bytes, usando el secreto como texto: la cadena de 64 caracteres tal como se muestra, sin decodificarla desde hexadecimal.
- Añade el prefijo
sha256=y compara el resultado con la cabeceraX-AutoPSEO-Signatureen tiempo constante. - Rechaza la petición si no coinciden. En caso contrario, responde enseguida con un estado 2xx y deja el trabajo lento para después.
Un receptor para Node.js sin dependencias:
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';
const secret = process.env.AUTOPSEO_WEBHOOK_SECRET;
createServer((req, res) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const raw = Buffer.concat(chunks);
const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(raw).digest('hex'));
const received = Buffer.from(String(req.headers['x-autopseo-signature'] || ''));
if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
res.writeHead(401).end();
return;
}
res.writeHead(204).end();
const event = JSON.parse(raw.toString('utf8'));
console.log(event.event_type, event.severity, event.site_url);
});
}).listen(8080);
La comprobación de longitud va primero porque timingSafeEqual de Node lanza una excepción cuando los dos búferes tienen longitudes distintas.
Reglas de entrega
- Cada mensaje tiene un solo intento con un tiempo de espera de 10 segundos. Cualquier respuesta que no sea 2xx cuenta como fallo y se conserva en el Historial de entregas con el código de estado y el principio del cuerpo de tu respuesta.
- No se siguen las redirecciones. Una respuesta 3xx falla con una nota que indica que uses la URL final.
- No se reintenta nada de forma programada. Una alerta fallida no se marca como enviada, de modo que su siguiente comprobación la evalúa de nuevo y la publica si sigue activa: a la mañana siguiente en la mayoría de los detectores, a la hora siguiente en los cambios de indexación. Un informe programado fallido no se reenvía dentro del mismo periodo, así que usa Enviar ahora después de arreglar el endpoint.
idse crea por petición, por lo que identifica una entrega y no un evento. Para deduplicar alertas, combinaevent_type,site_urly la fecha deoccurred_at: una regla publica como máximo una vez por propiedad y día UTC, salvo que alguien ejecute Probar regla.
El secreto de firma
AutoPSEO muestra el secreto de firma una sola vez, al guardar el webhook, y la lista no vuelve a mostrarlo. Guárdalo de inmediato en la configuración de tu receptor, junto al endpoint que protege.
La URL se almacena cifrada y la lista solo muestra su host y los últimos caracteres. Al eliminar un webhook también se eliminan todas las reglas que publican en él. Si prefieres una tarjeta legible en un canal de Teams en lugar de JSON, usa Microsoft Teams.