Registros de Discord webhook desde un servidor FiveM: guía de PerformHttpRequest

Envía registros a Discord desde FiveM con PerformHttpRequest e inserciones JSON. Solo lado del servidor, URL en un convar, una cola para límites de velocidad, y qué registrar.

Quieres saber quién compró qué, quién dio dinero, quién ejecutó un comando de administrador y quién se unió, sin mirar la consola todo el día. Un webhook de Discord lo hace: tu servidor publica un mensaje en un canal. Esta guía muestra el lado del servidor Lua con PerformHttpRequest, cómo construir inserciones, cómo mantener la URL segura y cómo evitar el límite de velocidad.

Crea el webhook

En Discord, abre la configuración del canal, luego Integraciones, luego Webhooks, crea uno y copia su URL. Se parece a https://discord.com/api/webhooks/<id>/<token>. Cualquiera con esa URL puede publicar en tu canal, así que trátala como una contraseña.

Mantén la URL en un convar

Ponlo en server.cfg:

cfg
set discord_webhook_logs "https://discord.com/api/webhooks/ID/TOKEN"

Usa set, no setr o sets. Un convar replicado se envía a cada cliente, que filtraría la URL. Léelo en el servidor:

lua
local WEBHOOK = GetConvar('discord_webhook_logs', '')

Si el convar está vacío, salta el envío en lugar de dar error. Puedes mantener un convar por tipo de registro (dinero, admin, uniones) para enviarlos a diferentes canales.

Envía un mensaje

lua
local function sendLog(title, description, color, fields)
    if WEBHOOK == '' then return end

    local embed = {
        title = title,
        description = description,
        color = color or 3447003,
        fields = fields,
        timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ'),
    }

    PerformHttpRequest(WEBHOOK, function(status, body, headers)
        if status ~= 204 and status ~= 200 then
            print(('webhook failed: %s'):format(status))
        end
    end, 'POST', json.encode({ username = 'Server logs', embeds = { embed } }), {
        ['Content-Type'] = 'application/json',
    })
end

Puntos a saber:

  • color es un número decimal, no una cadena hex. 3447003 es azul y 15158332 es rojo. Convierte hex con tonumber('FF0000', 16).
  • timestamp debe ser una fecha ISO 8601 en UTC. El ! en el formato hace que os.date use UTC.
  • fields es una lista de { name = '...', value = '...', inline = true }. Un valor de campo no puede estar vacío.
  • Una llamada webhook exitosa devuelve el estado 204, con un cuerpo vacío.
  • Discord limita los tamaños. Los títulos son cortos (256 caracteres), las descripciones son largas pero limitadas (4096), y un mensaje contiene un número limitado de campos e inserciones. Corta texto largo antes de enviarlo.

Un ejemplo de llamada, para un jugador que envió dinero:

lua
sendLog('Money transfer', 'A player sent money', 3066993, {
    { name = 'From', value = ('%s (id %d)'):format(GetPlayerName(src), src), inline = true },
    { name = 'To', value = ('%s (id %d)'):format(GetPlayerName(target), target), inline = true },
    { name = 'Amount', value = ('$%d'):format(amount), inline = false },
})

Solo del lado del servidor, nunca desde el cliente

Cuidado: no llames a PerformHttpRequest para un webhook en un script de cliente, y no envíes la URL al cliente. Los archivos del cliente pueden ser leídos por cada jugador, así que la URL se filtra y cualquiera puede inundar tu canal.

Si un evento en el cliente debe ser registrado, envía un evento al servidor, y deja que el servidor decida qué registrar desde datos en los que confía. Un cliente que puede activar «registra este texto» también puede activar registros falsos. Para la seguridad de eventos en general, lee eventos seguros en FiveM. Registra lo que hizo el servidor, no lo que el cliente dice que hizo.

Límites de velocidad: usa una cola

Discord rechaza llamadas de webhook que llegan demasiado rápido, con una respuesta 429. Una ráfaga de registros, como 50 jugadores activando el mismo evento, puede golpearla. Envía los mensajes desde una cola a un ritmo constante:

lua
local queue = {}

local function enqueue(payload)
    queue[#queue + 1] = payload
end

CreateThread(function()
    while true do
        local item = table.remove(queue, 1)
        if item then
            PerformHttpRequest(WEBHOOK, function(status, _, headers)
                if status == 429 then
                    -- put it back and wait a bit longer
                    table.insert(queue, 1, item)
                    Wait(5000)
                end
            end, 'POST', json.encode(item), { ['Content-Type'] = 'application/json' })
        end
        Wait(2000)
    end
end)

Esperar dos segundos entre mensajes se mantiene bajo el límite. Si necesitas más, agrupa varias inserciones en un mensaje: una solicitud puede llevar hasta 10 inserciones en su lista embeds. Para el uso de Wait, consulta Wait explicado.

Qué registrar

Registra los eventos que querrías cuando algo vaya mal:

  • Dinero: transferencias grandes, compras, cualquier cosa que cree dinero. Aquí es donde el abuso se muestra primero.
  • Acciones de administrador: quién usó qué comando de administrador en quién, bans, advertencias, teleports. Consulta banear y advertir jugadores.
  • Uniones y salidas, con los identificadores del jugador, lo que ayuda con disputas y bans.
  • Generación de elementos y uso de armas, si los permites.

No registres todo. Un canal con un mensaje cada segundo es un canal que nadie lee. Elige unos pocos canales, pon los importantes donde el personal mira, y mantén el resto corto. Mantén los datos personales a lo que necesitas, y no registres chat privado.

Consejo: pon tu función de registro en un pequeño recurso y llámala con una exportación desde tus otros scripts, así la URL del webhook y la cola de límite de velocidad viven en un lugar. Consulta exportaciones en FiveM.

Lista de verificación

Síntoma Solución
Nada llega Comprueba el nombre del convar, que la URL esté completa y que la llamada se ejecute en el servidor
Estado 400 El JSON es inválido: valor de campo vacío, texto demasiado largo, o una cadena de color hex
Estado 429 Envía a través de una cola con una pausa entre mensajes
La marca de tiempo es ignorada Usa UTC ISO 8601: os.date('!%Y-%m-%dT%H:%M:%SZ')
URL filtrada Elimina el webhook en Discord, crea uno nuevo, y mantenlo en un convar set
Demasiados mensajes Registra solo dinero, acciones de administrador y uniones

Respuestas rápidas

¿Puedo enviar un webhook de Discord desde el cliente?

No. Cualquiera puede leer el código del cliente y robar la URL del webhook, luego enviar spam a tu canal. Activa el registro en el servidor y envíalo desde allí.

¿Cuántos mensajes de webhook puedo enviar?

Discord limita los webhooks, y aproximadamente 30 mensajes por minuto por webhook es un techo seguro. Más allá de eso obtienes una respuesta 429, así que encola tus mensajes y envíalos a un ritmo constante.

¿Dónde debo guardar la URL del webhook?

En un convar del servidor establecido con set en server.cfg, leído con GetConvar. Nunca lo pongas en un archivo del cliente o en un convar establecido con setr o sets.

Scripts que evitan este problema

Advanced BoostingBoosting de vehículos desde una tablet: contratos de clase D a S+, crews y cola en vivo.Ver script →Crypto MiningCompra una nave, monta rigs pieza a pieza y mina monedas en un mercado vivo.Ver script →Shop CreatorCrea una tienda en menos de un minuto — dueños, empleados, caja fuerte y atracos.Ver script →

Sigue leyendo