Funciones
Alertas de espera
Waity llama a tu webhook cuando la espera en vivo de un lugar baja de tu umbral, para que un agente no tenga que preguntar una y otra vez. Todos los planes las incluyen, también Free.
Cómo funciona
Crea una alerta para un lugar con un umbral en minutos y un webhook https. Después de cada minuto de ingesta, Waity revisa los lugares que acaban de recibir lecturas nuevas y envía por POST un cuerpo JSON firmado a tu webhook cuando la espera está en el umbral o por debajo.
Una alerta lee la misma respuesta en vivo que GET /v1/places/{id}, así que nunca contradice a la API. Solo se dispara cuando now.usable es true, la unidad son minutos y now.value es como máximo under. Un límite inferior (“más de 60”), un número desactualizado o en pausa, un lugar cerrado o la falta de datos nunca la disparan.
Crear una alerta
POST /v1/alerts con una clave de API y un cuerpo JSON. La respuesta es la alerta, con su id y el secreto de firma. El secreto se muestra una sola vez: guárdalo donde tu webhook pueda leerlo.
| Campo | Qué admite |
|---|---|
place_id | Un lugar, carril o departamento. Un aeropuerto, un puerto fronterizo o un campus hospitalario no tiene una espera única, así que su id se rechaza indicando los ids de sus carriles o departamentos. |
under | Minutos enteros, de 1 a 600. |
webhook_url | Adónde envía Waity el POST. Solo https; consulta las reglas más abajo. |
repeat | false (por defecto) se dispara una vez y luego la alerta pasa a fired. true se vuelve a disparar después de que la espera suba por encima del umbral más 5 minutos, como máximo una vez cada 30 minutos. |
expires_at | Opcional, ISO 8601. Por defecto, 24 horas desde ahora; como máximo, 30 días. |
curl -X POST "https://api.waity.ai/v1/alerts" \
-H "X-Api-Key: $WAITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"place_id":"pl_nvk0wkb3ps","under":30,"webhook_url":"https://example.com/waity/webhook"}'
201Respuesta
{
"id": "al_3k9qx2m7t4p8w1za",
"place": {
"id": "pl_nvk0wkb3ps",
"name": "Ysleta Passenger Vehicles, Standard",
"category": "border"
},
"under": 30,
"unit": "min",
"repeat": false,
"webhook_url": "https://example.com/waity/webhook",
"status": "armed",
"created_at": "2026-10-31T19:02:11Z",
"expires_at": "2026-11-01T19:02:11Z",
Reglas de la URL del webhook
Para que las alertas no lleguen a redes privadas, Waity rechaza una URL de webhook que:
- no use https en el puerto por defecto (443), o incluya un nombre de usuario o una contraseña;
- use una dirección IP en lugar de un nombre de host, por lo que se rechazan todas las direcciones privadas, de loopback, link-local y de metadatos de la nube;
- use localhost, un nombre de una sola etiqueta o un nombre privado o reservado como .local, .internal, .lan o .home.arpa;
- apunte al propio Waity.
Waity no resuelve nombres por su cuenta y nunca sigue una redirección: una respuesta 3xx cuenta como entrega fallida.
Listar, leer y eliminar
GET /v1/alerts devuelve tus alertas, las más recientes primero, con su estado y su última entrega. Las claves de una cuenta comparten una lista; una clave sin cuenta tiene la suya. GET /v1/alerts/{id} lee una, y DELETE /v1/alerts/{id} la elimina y detiene sus entregas.
curl
curl "https://api.waity.ai/v1/alerts" \
-H "X-Api-Key: $WAITY_API_KEY"
curl -X DELETE "https://api.waity.ai/v1/alerts/al_3k9qx2m7t4p8w1za" \
-H "X-Api-Key: $WAITY_API_KEY"
| Estado | Qué admite |
|---|---|
armed | Esperando a que la espera baje del umbral. |
fired | Una alerta de una sola vez que ya se disparó. |
expired | Pasó su expires_at sin dispararse. |
failing | El último intento de entrega falló; consulta last_delivery. Siguen reintentos mientras queden. |
disabled | 20 intentos de entrega fallidos seguidos. Crea una alerta nueva cuando tu webhook funcione. |
El webhook
Cada entrega es un POST con un cuerpo JSON. Lee primero warnings al usuario y después summary. now.value vale hasta now.usable_until; después, vuelve a consultar GET /v1/places/{id}.
POST
{
"type": "wait.under",
"alert": {
"id": "al_3k9qx2m7t4p8w1za",
"under": 30
},
"place": {
"id": "pl_nvk0wkb3ps",
"name": "Ysleta Passenger Vehicles, Standard",
"category": "border",
"url": "https://waity.ai/places/ysleta-passenger-vehicles-standard-kb3ps"
},
"now": {
"value": 25,
"unit": "min",
"measured_at": "2026-10-31T13:58:00-06:00",
"usable_until": "2026-10-31T15:28:00-06:00",
"basis": "estimate"
},
"summary": "About 25 min to enter the US, estimated by the border agency, updated 2 min ago.",
"warnings": [],
"links": {
"place": "https://api.waity.ai/v1/places/pl_nvk0wkb3ps",
"page": "https://waity.ai/places/ysleta-passenger-vehicles-standard-kb3ps",
"alert": "https://api.waity.ai/v1/alerts/al_3k9qx2m7t4p8w1za",
"docs": "https://waity.ai/docs/alerts"
}
}
Cada solicitud lleva estos encabezados:
Waity-Signature: t=<unix>,v1=<hex>:tes el momento de la firma, en segundos unix;v1es el HMAC-SHA256 de la cadena formada port, un punto y el cuerpo sin procesar, con el secreto completo de la alerta como clave.Waity-Alert: el id de la alerta.Waity-Delivery: el id de la entrega, el mismo en cada reintento, para que puedas descartar duplicados.
Verificar la firma
Comprueba la firma sobre los bytes sin procesar antes de analizar el JSON, compara en tiempo constante y rechaza un t de más de 5 minutos de antigüedad.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.WAITY_ALERT_SECRET; // whsec_…
// The raw body: the signature covers the exact bytes Waity sent.
app.post("/waity/webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = Object.fromEntries(
(req.get("Waity-Signature") ?? "").split(",").map((p) => p.split("=", 2)),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
const ok =
fresh &&
typeof parts.v1 === "string" &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body);
// Read event.warnings first, then event.summary.
res.status(204).end();
});
app.listen(3000);
Reintentos
Responde con cualquier 2xx en 10 segundos. Cualquier otra cosa es un intento fallido, que se reintenta unos 1, 5, 25 minutos después. Tras 20 intentos fallidos seguidos, la alerta se desactiva.
Límites por plan
Cada plan admite un número de alertas activas a la vez. Una más responde 409 alert_limit con un mensaje que indica el límite de tu plan. Cada solicitud de alertas cuenta como una llamada; las entregas nunca cuentan. Una clave sin cuenta cuenta como Free.
| Plan | Alertas activas |
|---|---|
| Free | 1 |
| Plus | 10 |
| Pro | 100 |
| Team | 1000 |
Urgencias y atención urgente
En las alertas de urgencias y atención urgente, warnings[0] de cada entrega es la línea del 911, dicha una vez, como en cada respuesta de urgencias.
Ver también
Después dice cuándo debería bajar una espera, según el historial. Cómo leer una respuesta explica cada campo, y cada campo está en la Referencia de la API.