You made a shop. The client tells the server "buy this item for 50 coins" and the server obliges. Then one day a player has 9 billion coins.
How? The exploiter fired your RemoteEvent with their own arguments.
Quick answer: The client is never trustworthy. Send intent ("I want to buy the Sword"), and let the server decide everything else: price, ownership, distance, cooldown.
The rule: exploiters control the client
Anything running on a player's machine can be modified. That includes every LocalScript and every value they send through a Remote. You can't stop them from firing it — you can only make the server ignore bad requests.
The bad way
-- ❌ The client decides the price and the reward
Remote.OnServerEvent:Connect(function(player, itemName, price)
player.leaderstats.Coins.Value -= price
giveItem(player, itemName)
end)An exploiter sends price = -999999 and gets rich.
The good way: 4 checks
1. Validate types
Arguments can be anything: nil, tables, a 200MB string.
if typeof(itemName) ~= "string" then return end2. Validate against server data
The server owns the item list and the prices. The client never sends a price.
local ITEMS = { Sword = 50, Shield = 80 }
local price = ITEMS[itemName]
if not price then return end3. Validate the player's state
Can this player actually do this right now?
local coins = player.leaderstats.Coins
if coins.Value < price then return endFor actions in the world (opening a door, picking up an item), also check distance between the character and the target.
4. Rate limit
Even a valid request can be spammed thousands of times a second.
local lastFire = {}
local function onCooldown(player, seconds)
local now = os.clock()
if lastFire[player] and now - lastFire[player] < seconds then
return true
end
lastFire[player] = now
return false
end
Players.PlayerRemoving:Connect(function(player)
lastFire[player] = nil
end)Put it together
Remote.OnServerEvent:Connect(function(player, itemName)
if onCooldown(player, 0.5) then return end
if typeof(itemName) ~= "string" then return end
local price = ITEMS[itemName]
if not price then return end
local coins = player.leaderstats.Coins
if coins.Value < price then return end
coins.Value -= price
giveItem(player, itemName)
end)The client can now say only one thing: "I'd like a Sword." Everything else is decided where exploiters can't reach.
Bonus: what NOT to do
- ❌ Don't create a Remote like
GiveCoins— even if you "hide" it, it can be fired. - ❌ Don't trust
playerarguments that the client sends. The first argument ofOnServerEventis the real sender; any other player reference from the client is just data. - ❌ Don't calculate damage, rewards or cooldowns on the client.