F5 StudioF5 Studio
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 sale0x95a5a6
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 order0x7f8c8d
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