Send signed outgoing webhooks

Fed by the records themselves, delivered only to public HTTPS addresses
checked at every attempt, retried, and switched off after repeated failures.
A submitter's details travel only to webhooks that opted into them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 09:42:39 +02:00
parent f93775e549
commit 9bdbf834f6
23 changed files with 1325 additions and 0 deletions

View File

@@ -1373,6 +1373,35 @@ Para que eso funcione, la comprobación de permisos es ahora una sola regla en
(`register_permission_check_method => false`): la de spatie respondía antes y
concedía el permiso sin preguntar al token.
### Webhooks salientes
```
/api/v1/webhooks CRUD (permiso webhooks.*, solo admin)
GET /api/v1/webhooks/{id}/deliveries qué se envió y cómo fue
POST /api/v1/webhooks/{id}/ping envío de prueba
POST /api/v1/webhook-deliveries/{id}/redeliver
```
Avisan a otro sistema de `application.received`, `content.published`,
`content.unpublished`, `review.requested` y `review.returned` (o `*`). Salen de
los propios registros —el historial ya distingue publicar de editar, venga del
panel, de la API o de un lote— y no de los controladores, así que no hay forma
de publicar algo sin que se avise.
Cada envío es un POST JSON `{id, event, created_at, data}` firmado: la cabecera
`X-Webhook-Signature` es `sha256=` más el HMAC-SHA256 de `{timestamp}.{body}`
con el secreto, y `X-Webhook-Timestamp` va aparte para que un envío capturado
no se pueda repetir más tarde. El secreto se genera solo (o se rota con
`rotate_secret`), se guarda cifrado y sale una única vez.
Las mismas defensas que el descargador de imágenes, porque la petición sale
desde dentro del stack: solo HTTPS, solo direcciones públicas —comprobado al
guardar y **otra vez en cada envío**, fijando la conexión a la dirección
comprobada—, sin seguir redirecciones. Cinco reintentos en unas dos horas y
media; veinte fallos seguidos lo apagan hasta que alguien lo vuelve a activar.
Los datos de quien escribe al buzón (nombre, correo, mensaje) solo viajan a los
webhooks con `include_personal_data`.
### Lotes e importación
```