Suscribir un webhook al registro de cambios
POST https://api.keenauth.com/v1/accounts/{account_id}/changelog-subscriptions · propietario de la cuenta o administrador
Registra un endpoint HTTPS propio que recibe un webhook changelog.entry_published cada vez que se publica una entrada del registro de cambios en una de las categorías elegidas. La solicitud se realiza con las credenciales de keenauth login: la cuenta debe ser aquella en la que ha iniciado sesión, y usted debe ser su propietario o administrador. Una cuenta puede tener como máximo 5 suscripciones habilitadas.
Cada suscripción tiene su propio secreto de firma, devuelto una sola vez en la respuesta. Las entregas siguen Standard Webhooks: cada solicitud incluye los encabezados webhook-id, webhook-timestamp y webhook-signature, y la firma (v1,<base64>) es un HMAC-SHA256 sobre <webhook-id>.<webhook-timestamp>.<body>. La clave HMAC son los 32 bytes que codifican los 43 caracteres posteriores al prefijo idp_whsec_ (base62, big-endian); los últimos 6 caracteres son una suma de control y no forman parte de la clave. Verifique la firma y rechace una marca de tiempo que esté demasiado alejada de la hora actual (las bibliotecas de Standard Webhooks permiten 5 minutos) antes de confiar en el cuerpo.
No incluya datos personales, credenciales ni tokens en la URL: se almacena con la suscripción y se muestra a todos los propietarios y administradores de la cuenta.
Solicitud
Encabezados:
| Encabezado | Obligatorio | Descripción |
|---|---|---|
Authorization |
sí | DPoP <access token>: el token de acceso que la CLI recibió al iniciar sesión. |
DPoP |
sí | Una prueba DPoP para esta solicitud (htm POST, htu la URL de esta solicitud), firmada con la clave a la que está vinculado el token de acceso; contiene el hash del token de acceso y el nonce del servidor. |
Content-Type |
sí | application/json. |
Idempotency-Key |
no | De 16 a 64 caracteres de A-Z, a-z, 0-9, _ y -. Repetir la solicitud con la misma clave y el mismo cuerpo en un plazo de 24 horas devuelve la misma suscripción sin el secreto, con secret_redacted: true y el encabezado Idempotent-Replayed: true. |
Cuerpo:
| Campo | Obligatorio | Descripción |
|---|---|---|
url |
sí | El endpoint: https, puerto 443, un nombre de host (no una dirección IP ni un nombre solo local como .localhost, .local o .internal), sin información de usuario ni fragmento, de como máximo 2048 bytes. |
categories |
no | Cualquiera de api, sdk, deprecation, security y incident, cada uno como máximo una vez. Omita el campo para recibir todas las categorías; un array vacío se rechaza. |
Respuesta
201 Created con Cache-Control: no-store:
| Campo | Descripción |
|---|---|
id |
La suscripción (chlg_…). |
account_id |
La cuenta (acct_…). |
url |
El endpoint. |
categories |
Las categorías recibidas, en el orden api, sdk, deprecation, security, incident. |
status |
enabled, o disabled cuando las entregas siguen fallando. |
created_at |
Cuándo se creó la suscripción (RFC 3339, UTC). |
signing_secret |
El secreto de firma (idp_whsec_…). Se devuelve solo en esta respuesta; guárdelo en su gestor de secretos. |
secret_redacted |
true en una repetición idempotente, que no incluye signing_secret. |
Errores
| Estado | Código | Cuándo |
|---|---|---|
| 400 | malformed_request |
El cuerpo no es JSON, o el encabezado Idempotency-Key está repetido, vacío o mal formado. |
| 400 | unknown_field |
El cuerpo tiene un campo distinto de url y categories. |
| 401 | unauthenticated |
No se envió ningún token de acceso. |
| 401 | invalid_token |
El token de acceso no es válido, ha caducado o no es el de la CLI; inicie sesión de nuevo con keenauth login. |
| 401 | invalid_dpop_proof |
La prueba DPoP falta, está repetida o no coincide con la solicitud ni con la clave del token. |
| 401 | use_dpop_nonce |
Reintente con una prueba que incluya el nonce del encabezado DPoP-Nonce de la respuesta. |
| 401 | insufficient_user_authentication |
Se requiere autenticación multifactor (o, para propietarios y administradores, una clave de acceso o una clave de seguridad); inicie sesión de nuevo. |
| 403 | insufficient_permissions |
No es propietario ni administrador de la cuenta. |
| 403 | account_restricted |
El estado de la cuenta no permite cambios. |
| 403 | quota_exceeded |
La cuenta ya tiene 5 suscripciones habilitadas (errors[].code changelog_subscription_limit_exceeded). |
| 404 | not_found |
La cuenta no existe, no es la cuenta en la que ha iniciado sesión, o no es miembro de ella. |
| 413 | payload_too_large |
El cuerpo supera 8 KiB. |
| 415 | unsupported_media_type |
El cuerpo no es application/json. |
| 422 | validation_failed |
url falta o no es válido, una categoría es desconocida o está repetida, categories está vacío, o un campo tiene un tipo incorrecto. |
| 422 | idempotency_key_reuse |
Se usó Idempotency-Key con otro cuerpo en un plazo de 24 horas. |
| 429 | rate_limited |
Se crearon más de 10 suscripciones para la cuenta en la última hora; espere Retry-After. |
Entrega de webhooks
Cuando se publica una entrada, KeenAuth envía un POST a la URL de cada suscripción habilitada cuyas categorías incluyan alguna de las categorías de la entrada. La misma entrada nunca se envía dos veces a una misma suscripción.
Solicitud
| Encabezado | Descripción |
|---|---|
Content-Type |
application/json. |
webhook-id |
El identificador del evento id (evt_…). Es el mismo en cada reintento de la misma entrada, así que úselo para ignorar duplicados. |
webhook-timestamp |
Tiempo Unix en segundos en el que se firmó este intento. Es nuevo en cada intento. |
webhook-signature |
v1,<base64>: el HMAC-SHA256 descrito arriba, sobre <webhook-id>.<webhook-timestamp>.<body>. |
El cuerpo tiene como máximo 16 KiB. KeenAuth no sigue redirecciones.
Evento
{
"id": "evt_01jb0000000000000000000000",
"type": "changelog.entry_published",
"schema_version": 1,
"occurred_at": "2026-11-04T00:00:00Z",
"recorded_at": "2026-11-04T08:30:12Z",
"data": {
"id": "2026-11-04-example",
"url": "https://docs.keenauth.com/en/changelog/#2026-11-04-example",
"title": "Example entry",
"date_published": "2026-11-04T00:00:00Z",
"tags": ["deprecation"],
"categories": ["api", "deprecation"],
"surfaces": [{"kind": "api", "name": "management-v1", "version": "v1"}],
"breaking": false,
"sunset_at": "2027-11-04"
}
}
| Campo | Descripción |
|---|---|
id |
El evento (evt_…), igual al encabezado webhook-id. |
type |
Siempre changelog.entry_published. |
schema_version |
Siempre 1. Pueden añadirse nuevos campos opcionales a data dentro de la versión 1; ignore los campos que no conozca. |
occurred_at |
Cuándo se publicó la entrada (RFC 3339). |
recorded_at |
Cuándo puso KeenAuth en cola la entrega (RFC 3339). |
data.id, data.url, data.title, data.date_published |
La entrada, tal como aparece en los feeds del registro de cambios. |
data.tags |
Una o más de added, changed, fixed, removed, deprecation, security, breaking y incident. |
data.categories |
Las categorías de suscripción a las que pertenece la entrada. |
data.surfaces |
Las superficies afectadas: kind (api, sdk, cli, provider, mcp o docs), name y, cuando se conoce, version. |
data.breaking |
true cuando el cambio es disruptivo. |
data.sunset_at |
La fecha de retirada de una deprecación (YYYY-MM-DD); en caso contrario, null. |
El esquema legible por máquina es changelog.entry_published.v1.
Respuestas y reintentos
Responda con cualquier estado 2xx en un plazo de 15 segundos. El cuerpo de la respuesta se ignora. Cualquier otro resultado es un intento fallido y se reintenta: un error de conexión o de TLS, un tiempo de espera, una redirección, 429, 5xx y cualquier otro estado distinto de 410. El primer intento se realiza de inmediato; los siguientes, unos 1 minuto, 5 minutos, 30 minutos, 2 horas, 8 horas, 24 horas y 36 horas después del fallo anterior (cada intervalo varía hasta un 10 por ciento), 8 intentos en unos 3 días en total.
Si el octavo intento falla, la entrega se abandona y la suscripción se deshabilita (status disabled). Las entregas que ya estaban en cola para ella se cancelan.
410 Gone
Responda 410 Gone para indicar a KeenAuth que ya no desea recibir entregas. La suscripción se deshabilita de inmediato, sin más intentos, y sus entregas en cola se cancelan. Una suscripción deshabilitada no vuelve a habilitarse y no cuenta para el límite de 5; cree una nueva para volver a recibir entradas. Cada deshabilitación se registra en el registro de auditoría de la cuenta como changelog.subscription_disabled, con el motivo receiver_gone, permanent_failure o schedule_exhausted.
Ejemplo
curl -X POST https://api.keenauth.com/v1/accounts/acct_01jb0000000000000000000000/changelog-subscriptions \
-H "Authorization: DPoP $ACCESS_TOKEN" \
-H "DPoP: $DPOP_PROOF" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: changelog-hooks-2026-10" \
-d '{"url": "https://hooks.example.com/keenauth/changelog", "categories": ["deprecation", "security"]}'