Logo

Send FiveM server logs to Discord via Webhooks

Send FiveM server logs to Discord via Webhooks

Log critical events from your FiveM server (kills, kicks, bans, economy transactions) to a Discord channel. Full setup on Wisp at VeryCloud with custom resource or existing solution.

Introduction

Having server logs in Discord = near real-time monitoring, accessible to your whole staff, no panel needed. Combined with Discord mobile notifications, you know immediately if something goes wrong. This guide shows you how to do it cleanly on your VeryCloud Wisp FiveM.

Prerequisites

  • A FiveM server at VeryCloud
  • A Discord server where you have admin rights
  • Access to Files in Wisp

Step 1: Create the Discord webhook

  1. Open your Discord server
  2. Create a channel (e.g. #fivem-logs)
  3. Right-click → Edit ChannelIntegrationsWebhooks
  4. New Webhook → name it, copy the URL

Keep this URL secret: anyone with it can post in your channel.

Step 2: Choose an approach

Several options:

ApproachDifficultyFlexibility
Existing resource (logs-system, ox_logger)EasyLimited to predefined events
Custom Lua loggerMediumTotal
External bridge (Node.js + RCON)More complexVery flexible

This guide shows option 2 — most versatile.

Step 3: Create the logger resource

In Wisp → Files/resources/[utils]/discord_logger/:

fxmanifest.lua:

fx_version 'cerulean'
game 'gta5'
author 'VeryCloud'
description 'Discord webhook logger'
version '1.0.0'

server_scripts {
    'config.lua',
    'logger.lua'
}

config.lua:

Config = {}

Config.Webhooks = {
    general    = 'https://discord.com/api/webhooks/.../GENERAL',
    admin      = 'https://discord.com/api/webhooks/.../ADMIN',
    economy    = 'https://discord.com/api/webhooks/.../ECONOMY',
    moderation = 'https://discord.com/api/webhooks/.../MODERATION',
}

Config.BotName = 'VeryCloud Logs'
Config.BotAvatar = 'https://verycloud.fr/static/brand.svg'

logger.lua:

local function send(category, embed)
    local url = Config.Webhooks[category]
    if not url then return end

    PerformHttpRequest(url, function(err, _, _)
        if err and err ~= 204 then
            print('[discord_logger] HTTP error: ' .. err)
        end
    end, 'POST', json.encode({
        username = Config.BotName,
        avatar_url = Config.BotAvatar,
        embeds = { embed }
    }), { ['Content-Type'] = 'application/json' })
end

exports('log', function(category, title, description, color, fields)
    send(category, {
        title = title,
        description = description,
        color = color or 3447003,
        fields = fields or {},
        timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ'),
        footer = { text = GetCurrentResourceName() }
    })
end)

Step 4: Add the resource

In /server.cfg:

ensure discord_logger

Restart.

Step 5: Use from other resources

From any resource:

exports.discord_logger:log(
    'moderation',
    'Player kick',
    'Mathys kicked PlayerXYZ',
    15158332,  -- red
    {
        { name = 'Moderator', value = 'Mathys', inline = true },
        { name = 'Player', value = 'PlayerXYZ', inline = true },
        { name = 'Reason', value = 'AFK too long', inline = false }
    }
)

Step 6: Hook native FiveM events

Log connections/disconnections automatically:

AddEventHandler('playerConnecting', function(name, _, deferrals)
    local id = source
    local identifiers = GetPlayerIdentifiers(id)
    exports.discord_logger:log('general', 'Connection', name, 3066993, {
        { name = 'Steam', value = identifiers[1] or 'N/A', inline = true },
        { name = 'Player ID', value = tostring(id), inline = true }
    })
end)

AddEventHandler('playerDropped', function(reason)
    local name = GetPlayerName(source) or 'Unknown'
    exports.discord_logger:log('general', 'Disconnect', name, 10038562, {
        { name = 'Reason', value = reason, inline = false }
    })
end)

Step 7: Log admin commands

RegisterCommand('kick', function(source, args)
    if IsPlayerAceAllowed(source, 'command.kick') then
        local target = tonumber(args[1])
        local reason = table.concat(args, ' ', 2)
        DropPlayer(target, reason)

        exports.discord_logger:log('moderation',
            'Admin kick',
            ('%s kicked %s'):format(GetPlayerName(source), GetPlayerName(target)),
            15158332,
            { { name = 'Reason', value = reason } }
        )
    end
end)

Step 8: Rate-limiting

Discord webhook: 30 requests / minute / webhook. Beyond = 429 Too Many Requests.

Batch logs on busy servers:

local queue = {}
local flushInterval = 5

CreateThread(function()
    while true do
        Wait(flushInterval * 1000)
        for category, embeds in pairs(queue) do
            if #embeds > 0 then
                send(category, { embeds = embeds })
                queue[category] = {}
            end
        end
    end
end)

Step 9: Webhook security

  • Never commit webhook URL to Git (add config.lua to .gitignore)
  • Regenerate the URL if leaked (Discord → Edit Webhook → Reset)
  • Private channel for staff only
  • Never log sensitive data (passwords, full payments, personal data): GDPR

Step 10: Turnkey alternatives

If you don't want to code:

  • screenshot-basic + logs-system: free resources that do this
  • ox_logger (Overextended): integrated to ox frameworks
  • es_extended ships a configurable Discord logging system

For quick setup, use these. For fine control, your custom logger above.

Troubleshooting

No Discord message, no error — wrong webhook URL (paste exactly from Discord). Channel deleted / webhook revoked. 429 Too Many Requests — too many logs per minute → batch or reduce verbosity. Broken embed — Discord limits fields (1024 chars / field, 6000 chars total). Truncate long texts.

Useful commands

# Test a webhook from CLI
curl -X POST -H "Content-Type: application/json" \
  -d '{"content":"CLI test"}' \
  https://discord.com/api/webhooks/.../...

# Reset a webhook Discord-side
# -> Edit Channel / Integrations / Webhooks / [...] / Reset URL

Conclusion

Discord logger = mandatory for any community FiveM server. 30 minutes to drop in a custom resource and your staff has total real-time visibility. Choose what you log carefully (GDPR), batch on heavy volume, keep webhooks private.

Going further: log levels (info/warn/error), threshold alerting with @here, integration with external APM.

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members