Webhooks
La pestaña Webhooks de /nay (scope nay.admin) envía los eventos de los scripts a tus URL. Elige los eventos (o todos), pulsa Probar para comprobar la configuración y consulta la última respuesta de cada webhook.
Hay dos tipos:
- Discord: pega la URL de webhook de un canal de Discord (
https://discord.com/api/webhooks/...). Cada evento se convierte en un embed, con título y etiquetas en el idioma del servidor, y los campos del evento en orden. El markdown de Discord en los valores se escapa, no se menciona a nadie y los mensajes largos se cortan en el límite de 6000 caracteres. - JSON firmado: para tu propia web o tu bot. Cada webhook recibe un secreto, que se muestra al crearlo o al regenerarlo.
Envíos
Un envío es un POST de JSON:
json
{ "id": "3f9c2a1b7d4e8f60", "event": "nay_car_wash.wash", "resource": "nay_car_wash", "time": 1791670794, "data": { "player": "John", "price": 150 } }Cabeceras: X-Nay-Event, X-Nay-Delivery (igual que id), X-Nay-Timestamp (segundos Unix) y X-Nay-Signature: sha256=<hex>.
- Responde con cualquier estado 2xx. Si no hay respuesta en 15 segundos, cuenta como error de red.
- Los errores de red y las respuestas 5xx y 429 se reintentan tras 1, 10 y 60 segundos con el mismo
X-Nay-Delivery. Las demás respuestas 4xx no se reintentan. - Tras 20 envíos fallidos seguidos, el webhook se desactiva. Vuelve a activarlo en la pestaña, o guárdalo con una URL nueva.
- Los envíos a Discord no van firmados y respetan el
retry_afterde Discord cuando hay límite de frecuencia.
Eventos
Eventos de nay_lib:
| Evento | Cuándo | Datos |
|---|---|---|
nay.setting | Se cambia o restablece un ajuste. | resource, key, action, actor |
nay.permission | Se concede o retira una regla de permiso. | action, type, identifier, scope, actor |
nay.token | Se crea o revoca un token de API. | action, id, label, actor |
nay.test | El botón Probar. | message |
Cada script indica sus propios eventos en su página, por ejemplo Car Wash.
Verificar la firma
Antes de fiarte de un envío firmado, verifica su firma: HMAC-SHA256 de <X-Nay-Timestamp>.<raw body>, usando como clave el secreto exactamente como se muestra (el propio texto hexadecimal). Rechaza las marcas de tiempo antiguas.
Node.js (Express):
js
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
app.post("/nayretis", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-Nay-Timestamp") ?? "";
const expected = "sha256=" + createHmac("sha256", process.env.NAY_WEBHOOK_SECRET).update(`${timestamp}.${req.body}`).digest("hex");
const given = req.get("X-Nay-Signature") ?? "";
const valid = given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!valid || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(401);
}
const delivery = JSON.parse(req.body);
res.sendStatus(204);
});PHP:
php
<?php
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_NAY_TIMESTAMP'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, getenv('NAY_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_NAY_SIGNATURE'] ?? '') || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$delivery = json_decode($body, true);
http_response_code(204);