Skip to main content

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.

Server-only

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.

EventTriggerDefault color
listing_createA listing (buy-now or auction) is created🟢 0x2ecc71
listing_cancelA seller cancels an active listing🟠 0xe67e22
listing_expireA listing expires without a sale⚪ 0x95a5a6
bidA bid is placed on an auction🔵 0x3498db
bargain_offerA bargain / counter-offer is sent🟣 0x9b59b6
purchaseA sale completes (buy-now, auction win or bargain)🟢 0x1abc9c
order_dropoffA seller drops off the package🟡 0xf1c40f
order_pickupA buyer collects the package (sale finalized)🟢 0x27ae60
order_expireAn order expires (undelivered or unclaimed)🔴 0xe74c3c
return_claimA seller claims a returned/undelivered order⚫ 0x7f8c8d
reviewA buyer submits a review💗 0xe91e63
listing_price_changeA seller lowers a buy-now price🟡 0xf1c40f
admin_auditAn 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:

config_webhooks.lua
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:

config_webhooks.lua
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:

  1. events[name].url (per-event override)
  2. Webhooks.defaultUrl
  3. If neither is set → the event is skipped (logged to the WEBHOOK debug 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:

config_webhooks.lua
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​

config_webhooks.lua
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:

config_webhooks.lua
Webhooks.queue = {
tickMs = 250,
maxSize = 200,
retryAfterMaxSec = 30,
requestTimeoutMs = 10000,
retry5xxOnce = true,
}
OptionDefaultDescription
tickMs250Worker tick interval — ~4 requests/second, safely under Discord's limit
maxSize200Max events buffered. Beyond this the oldest event is dropped
retryAfterMaxSec30Upper clamp on Discord's Retry-After. A longer wait drops the event instead of blocking the queue
requestTimeoutMs10000Per-request HTTP timeout (ms)
retry5xxOncetrueRetry a 5xx server error once, then drop

Behaviour by HTTP response:

ResponseAction
2xxSuccess, 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
5xxRetry once (if retry5xxOnce), then drop
other 4xxDrop with a warning
0 (timeout/network)Drop with a transport error

URL Validation​

config_webhooks.lua
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:

config.lua
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​