> For the complete documentation index, see [llms.txt](https://docs.powermonitor.com.br/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.powermonitor.com.br/es/power-monitor/configuracoes/webhooks-e-api-publica.md).

# Webhooks y API pública (referencia técnica)

Referencia técnica de las integraciones de Power Monitor: formato del webhook, verificación de la firma HMAC, comportamiento de las entregas y la API pública de solo lectura con claves de API.

Esta página es la referencia para quien va a **implementar** las integraciones configuradas en [Configuración › Alertas](/es/power-monitor/configuracoes/alertas.md): recibir el webhook genérico, validar la firma y consumir la API pública. Para el paso a paso en la interfaz (crear endpoints, generar claves, probar), vea la página [Configuración › Alertas](/es/power-monitor/configuracoes/alertas.md).

{% hint style="warning" %}
Los ejemplos de esta página usan valores **ficticios** (claves, direcciones e identificadores). Nunca publique la clave de firma ni la clave de API en repositorios, correos o informes compartidos.
{% endhint %}

## Webhook genérico

El endpoint del tipo **Webhook genérico** envía un `POST` con cuerpo JSON (`Content-Type: application/json`) a la URL registrada, una vez por cada evento de alerta seleccionado.

### Eventos

| Evento (`event`)     | Cuándo se envía                                                                  |
| -------------------- | -------------------------------------------------------------------------------- |
| `alert.opened`       | Se abre un incidente.                                                            |
| `alert.recovered`    | El recurso se recupera (o el incidente se marca como resuelto).                  |
| `alert.escalated`    | El incidente quedó sin reconocer más allá del tiempo de la política de escalado. |
| `alert.acknowledged` | Alguien reconoció el incidente.                                                  |

Las reincidencias de un incidente ya abierto no generan un evento nuevo. Las alertas silenciadas o correlacionadas con una causa abierta no se envían.

### Cuerpo de la solicitud

```json
{
  "id": "6f0c1f0e-0000-0000-0000-000000000000",
  "event": "alert.opened",
  "occurredAt": "2026-10-05T12:00:00Z",
  "alert": {
    "id": "b1c2d3e4-0000-0000-0000-000000000000",
    "title": "Capacity F64",
    "message": "Mensaje de la alerta (hasta 1000 caracteres)",
    "sourceType": "Capacity",
    "sourceId": "00000000-0000-0000-0000-000000000000",
    "workspaceName": null,
    "capacityName": "Capacity F64",
    "severity": "error",
    "status": "open",
    "acknowledged": false,
    "openedAt": "2026-10-05T12:00:00Z",
    "resolvedAt": null,
    "failureCount": 1,
    "url": "https://<dirección-de-su-power-monitor>/monitoring/alert"
  }
}
```

* `id` es el identificador de la **entrega** (úselo para evitar procesar el mismo evento dos veces).
* `severity` es `error` o `info`; `status` es `open` o `resolved`.
* `sourceType` es el tipo del origen (por ejemplo `Gateway`, `Capacity`, `Dataset`, `FabricJob`, `Mirroring`, `DataFreshness`, `Connector`).
* En un envío de **prueba**, el cuerpo trae además `"test": true`.
* El cuerpo no incluye correos de usuarios ni identificadores internos de la organización.

### Encabezados

| Encabezado                 | Contenido                                                    |
| -------------------------- | ------------------------------------------------------------ |
| `X-PowerMonitor-Event`     | Nombre del evento (por ejemplo `alert.opened`).              |
| `X-PowerMonitor-Delivery`  | Identificador de la entrega.                                 |
| `Idempotency-Key`          | El mismo identificador de la entrega. Úselo para deduplicar. |
| `X-PowerMonitor-Timestamp` | Momento del envío, en segundos Unix. Cambia en cada intento. |
| `X-PowerMonitor-Signature` | `sha256=` seguido del HMAC-SHA256 en hexadecimal minúsculo.  |
| `User-Agent`               | `PowerMonitor-Webhook/1.0`.                                  |

Responda con cualquier estado **2xx** para confirmar la entrega.

### Cómo se calcula la firma

```
firma = "sha256=" + hex_minusculo( HMAC_SHA256( clave_de_firma, timestamp + "." + cuerpo_bruto ) )
```

* `clave_de_firma` es la clave mostrada una única vez al crear el endpoint (formato `whsec_...`). Úsela como texto, exactamente como se mostró.
* `timestamp` es el valor del encabezado `X-PowerMonitor-Timestamp`.
* `cuerpo_bruto` es el cuerpo de la solicitud **exactamente como se recibió**, antes de cualquier `JSON.parse`. Reserializar el JSON altera los bytes e invalida la firma.

### Cómo verificar (ejemplo)

Pasos recomendados en el receptor: leer el cuerpo bruto, recalcular la firma, comparar en **tiempo constante**, rechazar timestamps fuera de una ventana de tolerancia (300 segundos) y deduplicar por `Idempotency-Key`.

**Node.js (Express)**

```javascript
const crypto = require('crypto');
const express = require('express');

const SIGNING_SECRET = process.env.PM_SIGNING_SECRET; // ej.: whsec_EJEMPLO_NO_REAL
const app = express();

app.post('/hooks/power-monitor', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-PowerMonitor-Timestamp');
  const received = req.get('X-PowerMonitor-Signature') || '';
  const body = req.body.toString('utf8'); // cuerpo bruto

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);

  const expected = 'sha256=' + crypto
    .createHmac('sha256', SIGNING_SECRET)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  const deliveryId = req.get('Idempotency-Key'); // deduplique por este valor
  const event = JSON.parse(body);
  // ... procese el evento ...
  res.sendStatus(200);
});

app.listen(3000);
```

**Python**

```python
import hashlib, hmac, time

def is_valid(signing_secret: str, timestamp: str, raw_body: bytes, received_signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    material = timestamp.encode() + b"." + raw_body
    expected = "sha256=" + hmac.new(signing_secret.encode(), material, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_signature)
```

### Entrega, reintentos y límites

* **Límite de tiempo:** 10 segundos por llamada.
* **Reintentos:** hasta 5 intentos en total, con espera de 1 minuto, 5 minutos, 30 minutos y 2 horas entre ellos. Se repiten los fallos sin respuesta (tiempo agotado, red) y las respuestas 408, 429 y 5xx. Las respuestas 4xx (excepto 408 y 429) abandonan la entrega.
* **Procesamiento:** las entregas pendientes se procesan de forma continua (ciclos de cerca de 1 minuto), con un tope por organización y por ciclo para que una organización muy ruidosa no monopolice el envío. En picos, la entrega puede retrasarse.
* **Orden:** no hay garantía de orden entre eventos; use `occurredAt` y `alert.status` para decidir el estado final.
* **Historial:** el botón **Entregas** del endpoint muestra el resultado de cada entrega durante 30 días.
* **Seguridad del destino (protección contra SSRF):** solo se aceptan URLs **https** de hosts públicos, sin credenciales incrustadas. Hosts como `localhost`, nombres que terminan en `.internal` o `.local`, direcciones de loopback, de redes privadas (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local (169.254.0.0/16, que incluye el servicio de metadatos de la nube) e IPv6 equivalentes se rechazan, y la dirección se verifica de nuevo en el momento de la conexión. Las redirecciones (3xx) **no** se siguen y Power Monitor no usa proxy para estas llamadas. El mensaje de fallo de conexión es genérico a propósito.
* **Límite:** 20 endpoints por organización.

## Conectores de ITSM

Los tipos **PagerDuty**, **Opsgenie**, **ServiceNow** y **Azure DevOps** usan el mismo mecanismo de entrega (mismos eventos, filtros, reintentos y protecciones), pero hablan el formato de cada producto. El identificador de la alerta en Power Monitor se usa para agrupar los eventos del mismo incidente.

| Destino                       | Abierto                                                                               | Reconocido                         | Escalado                                                             | Recuperado                                                                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **PagerDuty** (Events API v2) | Dispara un evento (`trigger`) con clave de deduplicación igual al id de la alerta.    | `acknowledge` en la misma clave.   | Nuevo `trigger` marcado como `[ESCALADO]`, con severidad `critical`. | `resolve` en la misma clave.                                                                                                                           |
| **Opsgenie**                  | Crea la alerta (alias = id de la alerta).                                             | Reconoce la alerta.                | Agrega una nota.                                                     | Cierra la alerta.                                                                                                                                      |
| **ServiceNow** (Table API)    | Crea un incidente.                                                                    | Actualiza a "En curso" (estado 2). | Urgencia 1 y nota de trabajo.                                        | Actualiza a "Resuelto" (estado 6).                                                                                                                     |
| **Azure DevOps**              | Crea un work item de tipo Task en el proyecto indicado, con la etiqueta PowerMonitor. | Agrega un comentario al work item. | Agrega un comentario al work item.                                   | Agrega un comentario e intenta mover el work item a un estado de finalización (si el proceso del proyecto no lo acepta, solo se agrega el comentario). |

Notas:

* Si el evento de apertura aún no se entregó, los eventos siguientes del mismo incidente esperan y se reintentan (el sistema externo necesita la referencia creada en la apertura).
* El botón **Probar** abre un incidente de ejemplo y lo cierra a continuación. Si el cierre falla, la pantalla pide cerrarlo manualmente.
* Las credenciales (routing key, clave de API, token, usuario y contraseña, PAT) se almacenan cifradas y la interfaz nunca las devuelve.
* Los textos de los mensajes enviados a las herramientas de ITSM están en portugués.

## API pública

La API pública es de **solo lectura** (solo `GET`) y está bajo la ruta `/api/public/v1`, en la misma dirección de su Power Monitor. Existe para llevar datos a informes de Power BI y scripts, usando las [claves de API](/es/power-monitor/configuracoes/alertas.md#claves-de-api) creadas por un administrador.

### Autenticación

Envíe la clave en uno de los encabezados:

```
X-Api-Key: <SU_CLAVE_DE_API>
Authorization: ApiKey <SU_CLAVE_DE_API>
```

* La clave identifica a la **organización**: todas las consultas devuelven datos de la organización de la clave. No hay filtro por usuario ni por alcance de workspace.
* Cada clave solo accede a los endpoints de los alcances que recibió. Sin el alcance necesario, la respuesta es `403`.
* Clave ausente, inválida, expirada o revocada: `401` (el mensaje es el mismo en los cuatro casos). Organización suspendida: `403`.
* Muchos intentos con clave inválida desde la misma dirección (más de 10 por minuto) pasan a recibir `429`.
* **Límite de uso:** 60 solicitudes por minuto por clave (`429` al excederlo).
* Las respuestas no se almacenan en caché (`Cache-Control: no-store`).

### Formato de las respuestas

* JSON en camelCase, fechas y horas en ISO 8601 UTC, enumeraciones como texto.
* Las listas están paginadas y devuelven el envoltorio:

```json
{
  "value": [ ],
  "page": 1,
  "pageSize": 100,
  "totalCount": 250,
  "nextLink": "/api/public/v1/alerts?page=2&pageSize=100"
}
```

* `page` comienza en 1 (máximo 1000) y `pageSize` va de 1 a 500 (predeterminado 100). El `nextLink` es **relativo**: antepóngale la dirección de su Power Monitor. Repita la llamada hasta recibir `nextLink` vacío.
* Agregue `format=csv` a cualquier lista para recibir la misma página como archivo CSV (UTF-8 con BOM). El total y la paginación vienen en los encabezados `X-Total-Count`, `X-Page`, `X-Page-Size` y `X-Next-Link`.
* Los errores tienen el formato `{ "error": { "code": "...", "message": "..." } }`, con los códigos `invalid_request` (400), `unauthorized` (401) y `forbidden` (403).

### Endpoints

| Endpoint                            | Alcance                             | Parámetros                                                                                                                                                                                     | Qué devuelve                                                                                                                                                                                                                  |
| ----------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/public/v1/alerts`         | Alertas (`ReadAlerts`)              | `from`, `to` (ISO 8601 UTC; `from` inclusivo, `to` exclusivo; predeterminado: últimos 7 días; máximo 366 días), `status` (`open` o `resolved`), `sourceType`, `page`, `pageSize`, `format=csv` | Una alerta por incidente: `sourceType`, `sourceName`, `severity`, `notificationKind`, `status`, `message`, `createdAt`, `resolvedAt`, `acknowledgedAt`, `failureCount`, `lastOccurrenceAt`, `workspaceName`, `capacityName`.  |
| `GET /api/public/v1/alerts/metrics` | Métricas de alertas (`ReadMetrics`) | `from`, `to` (`yyyy-MM-dd`, días inclusivos; predeterminado: últimos 7 días; máximo 366)                                                                                                       | Totales del período: incidentes, reconocidos, resueltos, abiertos sin reconocer, silenciados, correlacionados, reincidencias, MTTA y MTTR (mediana, media y muestras), incidentes por tipo de origen y orígenes más ruidosos. |
| `GET /api/public/v1/capacities`     | Capacidades (`ReadCapacities`)      | `page`, `pageSize`, `format=csv`                                                                                                                                                               | `name`, `sku`, `state`, `region`.                                                                                                                                                                                             |
| `GET /api/public/v1/workspaces`     | Workspaces (`ReadWorkspaces`)       | `page`, `pageSize`, `format=csv`                                                                                                                                                               | `name`, `remoteId`, `capacityName`.                                                                                                                                                                                           |
| `GET /api/public/v1/costs/daily`    | Costos (`ReadCosts`)                | `from`, `to` (`yyyy-MM-dd`, días UTC inclusivos; predeterminado: últimos 30 días completos), `currency` (ISO 4217), `page`, `pageSize`, `format=csv`                                           | Costo diario por capacidad: `date`, `capacityName`, `currency`, `cost`, `amortizedCost`, `overageCost`. Una única moneda por respuesta (la indicada o la predominante en el período).                                         |
| `GET /api/public/v1/sla`            | SLA (`ReadSla`)                     | `month` (`yyyy-MM`; predeterminado: mes anterior; hasta 12 meses atrás)                                                                                                                        | Informe mensual de SLA: totales con metas y porcentajes de cumplimiento, MTTA y MTTR, incidentes por tipo, orígenes más ruidosos y disponibilidad por capacidad.                                                              |

### Ejemplos

**curl**

```bash
curl -H "X-Api-Key: <SU_CLAVE_DE_API>" \
  "https://<dirección-de-su-power-monitor>/api/public/v1/alerts?status=open&pageSize=100"
```

**Power BI (Power Query)**

```
let
    Response = Json.Document(Web.Contents("https://<dirección-de-su-power-monitor>/api/public/v1/alerts", [
        Headers = [#"X-Api-Key" = "<SU_CLAVE_DE_API>"],
        Query = [status = "open", page = "1", pageSize = "500"]
    ])),
    Items = Table.FromRecords(Response[value])
in
    Items
```

Para más de una página, llame de nuevo variando el parámetro `page`.

## Páginas relacionadas

* [Configuración › Alertas](/es/power-monitor/configuracoes/alertas.md): cómo crear endpoints y claves en la interfaz.
* [Monitoreo › Alertas](/es/power-monitor/monitoramento/alertas.md): reconocimiento, silenciamiento, escalado y métricas.
* [Informe de SLA](/es/power-monitor/monitoramento/relatorio-de-sla.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.powermonitor.com.br/es/power-monitor/configuracoes/webhooks-e-api-publica.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
