Skip to content

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_after de Discord cuando hay límite de frecuencia.

Eventos ​

Eventos de nay_lib:

EventoCuándoDatos
nay.settingSe cambia o restablece un ajuste.resource, key, action, actor
nay.permissionSe concede o retira una regla de permiso.action, type, identifier, scope, actor
nay.tokenSe crea o revoca un token de API.action, id, label, actor
nay.testEl 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);