Discord Webhooks
F5 Shadow Market ships with a full Discord audit-logging pipeline. Every meaningful market and admin action is dispatched to a Discord webhook as a localized rich embed, through a built-in rate-limited queue.
The entire system is configured in config_webhooks.lua.
config_webhooks.lua is loaded only on the server. Webhook URLs never reach the client. Do not move them into config.lua.
Events
Thirteen event types can be logged. Each has its own enabled flag, embed color, and optional per-event url.
| Event | Trigger | Default color |
|---|---|---|
listing_create | A listing (buy-now or auction) is created | 🟢 0x2ecc71 |
listing_cancel | A seller cancels an active listing | 🟠 0xe67e22 |
listing_expire | A listing expires without a sale | ⚪ 0x95a5a6 |
bid | A bid is placed on an auction | 🔵 0x3498db |
bargain_offer | A bargain / counter-offer is sent | 🟣 0x9b59b6 |
purchase | A sale completes (buy-now, auction win or bargain) | 🟢 0x1abc9c |
order_dropoff | A seller drops off the package | 🟡 0xf1c40f |
order_pickup | A buyer collects the package (sale finalized) | 🟢 0x27ae60 |
order_expire | An order expires (undelivered or unclaimed) | 🔴 0xe74c3c |
return_claim | A seller claims a returned/undelivered order | ⚫ 0x7f8c8d |
review | A buyer submits a review | 💗 0xe91e63 |
listing_price_change | A seller lowers a buy-now price | 🟡 0xf1c40f |
admin_audit | An admin performs a panel action (refund, ban, takedown, …) | 🔴 0xc0392b |
The admin_audit event only fires when F5Cfg.Admin.auditWebhook = true (see Admin Panel).
Minimal Setup
The fastest way to enable logging — one webhook for everything:
Webhooks.defaultUrl = 'https://discord.com/api/webhooks/XXXXXXXXXX/YYYYYYYYYY'
That's it. Every enabled event goes to defaultUrl.
Per-Event Webhook URLs
Each event can target a different channel by setting its own url. An empty url falls back to defaultUrl:
Webhooks.events = {
listing_create = { enabled = true, color = 0x2ecc71, url = '' },
bid = { enabled = true, color = 0x3498db, url = 'https://discord.com/api/webhooks/.../bids' },
purchase = { enabled = true, color = 0x1abc9c, url = 'https://discord.com/api/webhooks/.../sales' },
admin_audit = { enabled = true, color = 0xc0392b, url = 'https://discord.com/api/webhooks/.../staff' },
-- ... the remaining events
}
Resolution order for an event's target URL:
events[name].url(per-event override)Webhooks.defaultUrl- If neither is set → the event is skipped (logged to the
WEBHOOKdebug category when enabled)
Disabling Events
Disable a single event by flipping its enabled flag — it's dropped before queue insertion, so no request is made even if a URL is set:
Webhooks.events.bid.enabled = false
To silence the entire system, either clear all URLs (defaultUrl = '' and every events[*].url = '') or set every event's enabled = false.
Bot Identity
Webhooks.username = 'f5_shadowmarket logger'
Webhooks.avatarUrl = '' -- public image URL; empty = Discord default
username and avatarUrl are sent with every message (omitted if empty).
Embed Content (Localized)
Every embed title and field label comes from the locale files under webhook_* keys, following F5Cfg.Locale. So with F5Cfg.Locale = 'pl' the embeds arrive in Polish. Customize them in locales/<code>.lua — see Localization.
Each embed includes the event title (colored by events[name].color), event-specific fields (item, prices, parties, identifiers, etc.), a UTC timestamp and a footer. For example a purchase embed shows the order, listing, item, price, origin (buy-now / auction / bargain), buyer and seller; an order_pickup embed additionally breaks down the platform cut, listing fee and seller payout.
Queue / Rate Limiting
Discord rate-limits webhooks. F5 Shadow Market has a built-in queue + limiter:
Webhooks.queue = {
tickMs = 250,
maxSize = 200,
retryAfterMaxSec = 30,
requestTimeoutMs = 10000,
retry5xxOnce = true,
}
| Option | Default | Description |
|---|---|---|
tickMs | 250 | Worker tick interval — ~4 requests/second, safely under Discord's limit |
maxSize | 200 | Max events buffered. Beyond this the oldest event is dropped |
retryAfterMaxSec | 30 | Upper clamp on Discord's Retry-After. A longer wait drops the event instead of blocking the queue |
requestTimeoutMs | 10000 | Per-request HTTP timeout (ms) |
retry5xxOnce | true | Retry a 5xx server error once, then drop |
Behaviour by HTTP response:
| Response | Action |
|---|---|
2xx | Success, drop from queue |
429 (rate limited) | Honor Retry-After: if it exceeds retryAfterMaxSec the event is dropped; otherwise the URL is cooled down and the event requeued |
5xx | Retry once (if retry5xxOnce), then drop |
other 4xx | Drop with a warning |
0 (timeout/network) | Drop with a transport error |
URL Validation
Webhooks.urlValidationPattern = '^https://[%w%.%-]*discord[%w]*%.com/api/webhooks/'
URLs are soft-validated against this Lua pattern. A mismatch warns rather than hard-fails, so reverse-proxied setups still work. Accepted forms include discord.com, discordapp.com, canary.discord.com and ptb.discord.com.
Verifying
Enable webhook debug logs:
F5Cfg.Debug.enabled = true
F5Cfg.Debug.categories.WEBHOOK = true
You'll see enqueue / sent / skip lines in the server console, telling you exactly which event was dispatched (or why it was skipped). If an event is skipped for disabled, flip its enabled flag; if it's skipped for a missing URL, set events[name].url or defaultUrl.
See Also
- Admin Panel — the
admin_auditevent andauditWebhook - Localization — translating embed labels (
webhook_*keys) - Configuration → Debug — enabling the
WEBHOOKcategory - Troubleshooting — when embeds don't show up