Events & Handlers
Public event hooks third-party resources can listen to (XP, stats, gang loyalty, Discord logs…) without needing the source
🪝 Events & Handlers
suty-drugsell fires public events on key moments of the sale flow. Other
resources can listen to these events to add their own behavior — XP systems,
stat trackers, Discord webhooks, gang loyalty, achievements, leaderboards —
without modifying or shipping the suty-drugsell source.
Two events fire on a successful sale: one on the server, one on the seller's client. Both deliver the same payload table.
ℹ️ Today, only successful sales fire an event. Refused sales, failed validations, and "sale started" do not have hooks yet. See What does NOT fire at the bottom.
Server event — suty-drugsell:sold
Fires once per successful sale. Listen from any server-side script.
AddEventHandler('suty-drugsell:sold', function(src, payload)
-- src : number player server id (the seller)
-- payload : table
--
-- payload.drug : string -- item name sold (e.g. 'cocaine_bag')
-- payload.quantity : number -- units sold this transaction
-- payload.totalPrice : number -- total money received
-- payload.pricePerUnit : number -- random per-unit price used
-- payload.pedNet : number -- network id of the customer NPC
end)
It's a plain TriggerEvent (not a NetEvent), so only other server-side
resources can listen — players cannot spoof it from the client.
Client event — suty-drugsell:client:sold
Fires on the seller's client right after the sale completes. Useful for floating XP popups, sound effects, NUI notifications, screen flashes.
RegisterNetEvent('suty-drugsell:client:sold', function(payload)
-- Same payload as the server event (no `src` — it's the local player).
-- payload.drug : string
-- payload.quantity : number
-- payload.totalPrice : number
-- payload.pricePerUnit : number
end)
Examples
1. Award XP per sale (server)
AddEventHandler('suty-drugsell:sold', function(src, p)
local xp = math.floor(p.totalPrice * 0.1) -- 10% of revenue
exports['my-xp']:AddXp(src, 'dealing', xp)
end)
2. Per-drug XP rates (server)
local XP = {
weed_bag = 4,
cocaine_bag = 12,
meth_bag = 18,
}
AddEventHandler('suty-drugsell:sold', function(src, p)
local perUnit = XP[p.drug] or 5
exports['my-xp']:AddXp(src, 'dealing', perUnit * p.quantity)
end)
3. Track total dollars sold (server)
local total = {} -- [citizenid] = number
AddEventHandler('suty-drugsell:sold', function(src, p)
local Player = exports.qbx_core:GetPlayer(src)
if not Player then return end
local cid = Player.PlayerData.citizenid
total[cid] = (total[cid] or 0) + p.totalPrice
end)
4. Discord webhook log (server)
AddEventHandler('suty-drugsell:sold', function(src, p)
local name = GetPlayerName(src) or 'unknown'
PerformHttpRequest('https://discord.com/api/webhooks/...', function() end, 'POST',
json.encode({
embeds = {{
title = 'Drug Sale',
color = 15844367,
fields = {
{ name = 'Player', value = ('%s (src %d)'):format(name, src), inline = false },
{ name = 'Drug', value = p.drug, inline = true },
{ name = 'Amount', value = ('%dx'):format(p.quantity), inline = true },
{ name = 'Income', value = ('$%d'):format(p.totalPrice), inline = true },
},
}},
}),
{ ['Content-Type'] = 'application/json' })
end)
5. Floating XP popup on the client
RegisterNetEvent('suty-drugsell:client:sold', function(p)
local xp = math.floor(p.totalPrice * 0.1)
lib.notify({
title = 'Dealing XP',
description = ('+%d XP for selling %dx %s'):format(xp, p.quantity, p.drug),
type = 'success',
})
end)
6. Gang loyalty (server)
AddEventHandler('suty-drugsell:sold', function(src, p)
local Player = exports.qbx_core:GetPlayer(src)
if not Player then return end
local gang = Player.PlayerData.gang and Player.PlayerData.gang.name
if not gang or gang == 'none' then return end
exports['my-gangs']:AddLoyalty(gang, math.ceil(p.totalPrice / 50))
end)
Listening from a brand-new resource
A minimal add-on that listens to drug sales is just two files:
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'
client_script 'client.lua' -- only if you need the client hook
server.lua
AddEventHandler('suty-drugsell:sold', function(src, p)
-- your logic here
end)
client.lua
RegisterNetEvent('suty-drugsell:client:sold', function(p)
-- your UI here
end)
That's it. No dependency on suty-drugsell, no access to its source
required, and the add-on keeps working across updates as long as payload
field names stay the same (they will — new fields may be added but existing
ones won't be renamed or removed).
What does NOT fire
For transparency, these flows have no public hook today. Open a ticket on Discord if you need one and we'll add it:
- A sale that's refused (NPC undercover cop / police alert chance)
- A sale that fails validation (no drugs in inventory, not in zone, etc.)
- A player starting a sale conversation (before the deal lands)
- Police alerts triggered by a sale
Compatibility note
The events have been stable since version 1.1.0. If you're on an older
build, update before relying on them.
