# API de webhooks

> Crear, listar, obtener, actualizar, eliminar, rotar el secreto, probar y reenviar los webhooks salientes de un proyecto en la API para integradores.

Los webhooks salientes pertenecen a un proyecto. La API los crea y gestiona, envía eventos de prueba, y lista y reenvía las entregas de cada webhook. Para los eventos y las cargas útiles que recibe el receptor, lee [Webhooks salientes para desarrolladores](https://docs.probara.net/es/developers/webhooks/). Para el comportamiento en la aplicación, lee la [guía de webhooks](https://docs.probara.net/es/guides/webhooks/).

## Acceso y límites

- Las lecturas requieren el permiso `integrations.manage` (que tienen `owner`/`admin`).
- Las escrituras requieren el mismo permiso y un plan que incluya webhooks, **Pro** o **Enterprise**. En otros planes, las escrituras devuelven `403 forbidden`.
- Las escrituras tienen un límite de 120 por hora para cada organización. Por encima del límite, la API devuelve `429` con `too_many_requests`.
- `{projectId}` es el **código** del proyecto, como `CHK`, igual que en las demás rutas de proyecto.
- Las solicitudes de escritura aceptan el encabezado opcional `Idempotency-Key`. Consulta [Idempotency-Key](https://docs.probara.net/es/developers/api/idempotency/).
- Un proyecto tiene como máximo 20 webhooks, así que la lista no está paginada.

## Endpoints

| Método   | Ruta                                                                                      | Éxito                                   |
| -------- | ----------------------------------------------------------------------------------------- | --------------------------------------- |
| `GET`    | `/api/v1/projects/{projectId}/webhooks`                                                   | `200` lista de webhooks                 |
| `POST`   | `/api/v1/projects/{projectId}/webhooks`                                                   | `201` webhook y su secreto              |
| `GET`    | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}`                                     | `200` webhook                           |
| `PATCH`  | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}`                                     | `200` webhook                           |
| `DELETE` | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}`                                     | `204`                                   |
| `POST`   | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}/rotate-secret`                       | `200` secreto nuevo                     |
| `POST`   | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}/test`                                | `201` entrega de prueba                 |
| `GET`    | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries`                          | `200` página de entregas                |
| `GET`    | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries/{deliveryUlid}`           | `200` entrega con solicitud y respuesta |
| `POST`   | `/api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries/{deliveryUlid}/redeliver` | `202` entrega nueva                     |

## Listar

`GET /api/v1/projects/{projectId}/webhooks`

Devuelve todos los webhooks del proyecto como `{ items }`. Cada elemento sigue los [campos del webhook](#campos-del-webhook).

### Ejemplo

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks"
```

## Crear

`POST /api/v1/projects/{projectId}/webhooks`

### Cuerpo de la petición

| Campo       | Obligatorio   | Notas                                                                                                                                                                                                                                               |
| ----------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`      | sí            | De 1 a 120 caracteres                                                                                                                                                                                                                               |
| `url`       | sí            | URL pública `https://`, sin credenciales, IP ni nombre local, de hasta 2000 caracteres. No se siguen redirecciones.                                                                                                                                 |
| `events`    | según el caso | Nombres de evento únicos del catálogo. Obligatorio salvo que `allEvents` sea `true`.                                                                                                                                                                |
| `allEvents` | no            | `true` suscribe el webhook a todos los eventos, incluidos los que se añadan más adelante. Por defecto, `false`.                                                                                                                                     |
| `enabled`   | no            | Por defecto, `true`.                                                                                                                                                                                                                                |
| `method`    | no            | `POST` (por defecto), `PUT` o `PATCH`                                                                                                                                                                                                               |
| `headers`   | no            | Hasta 20 objetos con `name` y `value`. Cada nombre aparece una vez, sin distinguir mayúsculas. Se rechazan los nombres que Probara define por sí mismo y los que empiezan por `cf-`. Un valor tiene hasta 4096 caracteres, sin saltos de línea.     |
| `payload`   | no            | `{"format":"standard"}` (por defecto), o `{"format":"custom","contentType":"...","template":"..."}`. `contentType` por defecto es `application/json`. Una `template` tiene de 1 a 16.384 caracteres, y una plantilla JSON debe generar JSON válido. |
| `secret`    | no            | Un secreto `whsec_` que codifica de 24 a 64 bytes en base64. Omítelo para que Probara genere uno.                                                                                                                                                   |

### Respuesta

`201` con los [campos del webhook](#campos-del-webhook) y un campo `secret`. El secreto solo se devuelve aquí y al [rotar el secreto](#rotar-el-secreto). Guárdalo en ese momento.

Un cuerpo inválido devuelve `422 validation_failed`, por ejemplo sin eventos y sin `allEvents`, una URL que las reglas del endpoint rechazan, una plantilla no válida o un proyecto que ya tiene 20 webhooks.

### Ejemplo

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Alertas de release","url":"https://hooks.example.com/probara","allEvents":true}' \
  "https://app.probara.net/api/v1/projects/CHK/webhooks"
```

## Obtener

`GET /api/v1/projects/{projectId}/webhooks/{webhookUlid}`

Devuelve un webhook. Un `webhookUlid` desconocido devuelve `404 not_found`.

### Ejemplo

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J"
```

## Actualizar

`PATCH /api/v1/projects/{projectId}/webhooks/{webhookUlid}`

Actualización parcial. Se requiere al menos un campo. Para cambiar el secreto, usa [rotar el secreto](#rotar-el-secreto).

### Cuerpo de la petición

| Campo       | Obligatorio | Notas                                                                                                                                                                                         |
| ----------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`      | no          | De 1 a 120 caracteres                                                                                                                                                                         |
| `url`       | no          | Las mismas reglas que al crear                                                                                                                                                                |
| `events`    | no          | Una lista no vacía de nombres de evento, salvo que `allEvents` sea `true`                                                                                                                     |
| `allEvents` | no          | `true` o `false`                                                                                                                                                                              |
| `enabled`   | no          | `true` o `false`. Activar un webhook reinicia su contador de entregas fallidas.                                                                                                               |
| `method`    | no          | `POST`, `PUT` o `PATCH`                                                                                                                                                                       |
| `headers`   | no          | Las mismas reglas que al crear. La lista reemplaza los encabezados guardados: un nombre que no se incluye se elimina. Un encabezado sin `value` conserva el valor ya guardado con ese nombre. |
| `payload`   | no          | Las mismas reglas que al crear                                                                                                                                                                |

### Ejemplo

```bash
curl -sS -X PATCH \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}' \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J"
```

## Eliminar

`DELETE /api/v1/projects/{projectId}/webhooks/{webhookUlid}`

Devuelve `204` si se completa, y elimina con él el historial de entregas del webhook. Un `webhookUlid` desconocido devuelve `404 not_found`.

### Ejemplo

```bash
curl -sS -X DELETE \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J"
```

## Rotar el secreto

`POST /api/v1/projects/{projectId}/webhooks/{webhookUlid}/rotate-secret`

Reemplaza el secreto de firma. La respuesta devuelve el secreto nuevo una sola vez. El secreto actual deja de verificar las solicitudes en cuanto lo rotas, así que actualiza tu receptor con el nuevo secreto de inmediato.

### Respuesta

`200` con `{ "secret": "whsec_...", "secretHint": "..." }`. `secretHint` contiene los últimos cuatro caracteres del secreto nuevo.

### Ejemplo

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J/rotate-secret"
```

## Enviar una prueba

`POST /api/v1/projects/{projectId}/webhooks/{webhookUlid}/test`

Envía un evento de muestra al webhook, con `test` en `true`. La solicitud se intenta una vez, de forma síncrona, sin reintentos. Una prueba nunca cuenta para el contador de fallos.

### Cuerpo de la petición

| Campo       | Obligatorio | Notas                                                                                                                                                                              |
| ----------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eventType` | no          | Un nombre de evento del catálogo. El evento de muestra tiene este tipo. Por defecto, es el primer evento suscrito del webhook, o `test_case.created` cuando `allEvents` es `true`. |

### Respuesta

`201` con una [entrega detallada](#campos-de-la-entrega). `409 conflict` cuando el webhook está desactivado o el cuerpo de la muestra supera los 256 KiB.

### Ejemplo

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"eventType":"test_run.closed"}' \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J/test"
```

## Listar entregas

`GET /api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries`

Paginación por página.

### Parámetros de consulta

| Parámetro  | Notas                                                 |
| ---------- | ----------------------------------------------------- |
| `page`     | Número de página (desde 1, por defecto `1`)           |
| `pageSize` | Elementos por página (por defecto `50`, máximo `200`) |
| `status`   | `pending`, `succeeded` o `failed`                     |

### Respuesta

`200` con `{ items, page, pageSize, total }`. Cada elemento sigue los [campos de la entrega](#campos-de-la-entrega).

### Ejemplo

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J/deliveries?status=failed"
```

## Obtener una entrega

`GET /api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries/{deliveryUlid}`

Devuelve los [campos de la entrega](#campos-de-la-entrega) y, además, la solicitud que envió Probara y la respuesta que recibió. Los valores de los encabezados personalizados en `request.headers` aparecen como `[redacted]`.

### Ejemplo

```bash
curl -sS \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J/deliveries/01JC8Z3P9Q8R7S6T5V4W3X2Y1Z"
```

## Reenviar una entrega

`POST /api/v1/projects/{projectId}/webhooks/{webhookUlid}/deliveries/{deliveryUlid}/redeliver`

Envía el mismo evento otra vez como una entrega nueva, con el mismo ID de evento y el mismo cuerpo, a la URL, el método y los encabezados actuales del webhook. El campo `redeliveryOf` de la entrega nueva contiene el ULID de la entrega que reenvía. Los reenvíos cuentan para el contador de fallos.

### Respuesta

`202` con los [campos de la entrega](#campos-de-la-entrega) de la entrega nueva. `409 conflict` cuando el webhook está desactivado, o cuando el cuerpo de la entrega nunca se envió por superar los 256 KiB.

### Ejemplo

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects/CHK/webhooks/01JC8Z3N0A1B2C3D4E5F6G7H8J/deliveries/01JC8Z3P9Q8R7S6T5V4W3X2Y1Z/redeliver"
```

## Campos del webhook

Las respuestas de crear, obtener, listar y actualizar devuelven estos campos. Los tiempos son milisegundos Unix desde la época.

| Campo                 | Tipo                                      | Notas                                                                                                                                        |
| --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `ulid`                | cadena (ULID)                             | Identificador asignado por el servidor                                                                                                       |
| `projectUlid`         | cadena (ULID)                             | El proyecto al que pertenece el webhook                                                                                                      |
| `name`                | cadena                                    | Nombre visible                                                                                                                               |
| `url`                 | cadena                                    | URL del endpoint                                                                                                                             |
| `enabled`             | booleano                                  | Indica si se envían eventos                                                                                                                  |
| `disabledReason`      | `manual`, `failures` o `null`             | Por qué el webhook está desactivado: `manual` cuando una persona lo desactivó, `failures` cuando Probara lo desactivó tras entregas fallidas |
| `disabledAt`          | número o `null`                           | Cuándo se desactivó                                                                                                                          |
| `events`              | cadena[]                                  | Nombres de los eventos suscritos                                                                                                             |
| `allEvents`           | booleano                                  | `true` cuando el webhook recibe todos los eventos                                                                                            |
| `method`              | cadena                                    | `POST`, `PUT` o `PATCH`                                                                                                                      |
| `headers`             | `{ name }[]`                              | Solo los nombres de los encabezados. Los valores solo se escriben.                                                                           |
| `payload`             | objeto                                    | La configuración de la carga útil, como al crear                                                                                             |
| `secretHint`          | cadena                                    | Los últimos cuatro caracteres del secreto de firma                                                                                           |
| `consecutiveFailures` | número                                    | Entregas fallidas seguidas                                                                                                                   |
| `lastDeliveryAt`      | número o `null`                           | Cuándo se hizo la entrega más reciente                                                                                                       |
| `lastDeliveryStatus`  | `pending`, `succeeded`, `failed` o `null` | Estado de la entrega más reciente                                                                                                            |
| `lastSuccessAt`       | número o `null`                           | Cuándo se hizo la última entrega exitosa                                                                                                     |
| `createdAt`           | número                                    | Cuándo se creó el webhook                                                                                                                    |
| `updatedAt`           | número                                    | Cuándo se modificó por última vez el webhook                                                                                                 |

## Campos de la entrega

Las respuestas de entregas devuelven estos campos. Los tiempos son milisegundos Unix desde la época.

| Campo            | Tipo                              | Notas                                                                                                                                                    |
| ---------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ulid`           | cadena (ULID)                     | Identificador de la entrega                                                                                                                              |
| `webhookUlid`    | cadena (ULID)                     | El webhook                                                                                                                                               |
| `eventId`        | cadena (ULID)                     | El evento. Es igual al encabezado `webhook-id`.                                                                                                          |
| `eventType`      | cadena                            | El nombre del evento                                                                                                                                     |
| `test`           | booleano                          | `true` para una entrega de prueba                                                                                                                        |
| `redeliveryOf`   | cadena (ULID) o `null`            | La entrega que esta reenvía                                                                                                                              |
| `status`         | `pending`, `succeeded` o `failed` | Estado actual                                                                                                                                            |
| `attempts`       | número                            | Intentos realizados hasta ahora                                                                                                                          |
| `createdAt`      | número                            | Cuándo se creó la entrega                                                                                                                                |
| `lastAttemptAt`  | número o `null`                   | Cuándo se hizo el último intento                                                                                                                         |
| `nextAttemptAt`  | número o `null`                   | Cuándo vence el siguiente reintento                                                                                                                      |
| `responseStatus` | número o `null`                   | Estado HTTP del último intento                                                                                                                           |
| `durationMs`     | número o `null`                   | Cuánto duró el último intento                                                                                                                            |
| `error`          | cadena o `null`                   | Por qué la entrega falló sin una respuesta `2xx`, como un tiempo de espera agotado, un error de DNS o TLS, una redirección o un cuerpo de más de 256 KiB |

Una entrega detallada añade dos objetos:

| Campo      | Tipo            | Notas                                                                                                                                  |
| ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `request`  | objeto          | `method`, `url`, `headers` y `body` que envió Probara. Los valores de los encabezados personalizados aparecen como `[redacted]`.       |
| `response` | objeto o `null` | `status`, `headers` y `body` que devolvió el endpoint. El cuerpo se conserva hasta 4096 bytes, y `bodyTruncated` indica si se recortó. |

## Errores

| Estado | Código              | Cuándo                                                                                                                                                                  |
| ------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `unauthorized`      | El token falta, no es válido o fue revocado                                                                                                                             |
| `403`  | `forbidden`         | El token no tiene `integrations.manage`, o se intenta escribir en un plan sin webhooks                                                                                  |
| `404`  | `not_found`         | El webhook o la entrega no existe                                                                                                                                       |
| `409`  | `conflict`          | Se pide una prueba o un reenvío para un webhook desactivado, se pide un reenvío de una entrega cuyo cuerpo nunca se envió, o el cuerpo de una prueba supera los 256 KiB |
| `422`  | `validation_failed` | El cuerpo o un parámetro de consulta no es válido, incluida una URL que las reglas del endpoint rechazan, o el proyecto ya tiene 20 webhooks                            |
| `429`  | `too_many_requests` | Se superó el límite de frecuencia de escrituras                                                                                                                         |

## Eventos de auditoría

Crear, actualizar y eliminar un webhook, rotar su secreto y desactivarlo automáticamente generan `webhook.created`, `webhook.updated`, `webhook.deleted`, `webhook.secret_rotated` y `webhook.auto_disabled` en el feed de actividad de la organización. Ver [Eventos de auditoría](https://docs.probara.net/es/developers/api/audit-events/).
