Portable Discord webhook transport — Python package + multi-language reference.
Things you’ll want when you go past “hello world.”
The send is HTTPS — typically 100-300 ms from India. Don’t block your hot path. Wrap the send in a daemon thread + bounded queue:
# Python — drop alongside your DiscordSender
import queue, threading
_Q = queue.Queue(maxsize=500)
def _worker():
while True:
task = _Q.get()
try:
task()
finally:
_Q.task_done()
threading.Thread(target=_worker, daemon=True).start()
def anotify(channel, embed, content=""):
try:
_Q.put_nowait(lambda: sender.send_embed(channel, embed, content=content))
except queue.Full:
pass # drop overflow
In Node, use a simple in-memory queue plus a worker loop. In Go, a
buffered channel + goroutine. In Rust, tokio::spawn.
Bug fires same error 10,000× in 30 sec? You’ll burn the 5-per-sec rate
limit, fill #alerts with junk, and miss the next genuine alert.
Add a small in-memory throttle:
import time
_BUCKET: dict[str, list[float]] = {}
_WINDOW = 60.0
_MAX = 3
def throttle_allow(key: str) -> bool:
now = time.monotonic()
ts = [t for t in _BUCKET.get(key, []) if now - t < _WINDOW]
if len(ts) >= _MAX:
return False
ts.append(now)
_BUCKET[key] = ts
return True
# Key = severity + summary, NOT full error text
if throttle_allow(f"error:{ctx}:{exc.__class__.__name__}"):
notify(P1, "Error", str(exc))
Rule of thumb: 3 sends per key per 60 sec catches most storms without suppressing real recurring problems.
The Python package does this. Every language example in this repo does this. If you’re rolling your own, do it too. Without backoff a single burst will lose messages silently:
HTTP 429
{ "message": "You are being rate limited.", "retry_after": 0.5 }
Sleep retry_after + 0.05, retry up to 5 times.
For long-term searchability, fire every event a second time as a
compact one-liner to a dedicated #audit channel. Mute that channel —
you’ll never look at it day-to-day, but six months later when you need
to know what fired during last quarter’s incident, it’s a single
scrollable feed.
The Python package does this automatically when you set
audit_channel=Channel.AUDIT.
RED 0xFF0000 P0 — system down, page someone
ORANGE 0xFF8800 P1 — degraded, manual action soon
YELLOW 0xFFCC00 P2 — investigate when free
GREEN 0x00CC66 P3 — positive event (deploy, signup, fill)
BLUE 0x3399FF P3 — neutral event (trade, fill)
GRAY 0x808080 DEV — debug noise
WHITE 0xFFFFFF audit mirror
You scan colour, not text. Triage in under 1 second.
@everyone for P0 onlysender.send_embed(
Channel.ALERTS,
embed,
content="@everyone", # forces push even on @mentions-only setting
allowed_mentions={"parse": ["everyone"]},
)
If you use this for P1 too, your team learns to ignore @everyone. Save it.
allowed_mentions defaultIf you POST a message with content containing <@123...> or
@everyone and no allowed_mentions field, Discord pings literally
everyone the IDs match by default. Always set
allowed_mentions: {"parse": []} (or list only what you intend) when
your content might include mention syntax from user data.
discord\.com/api/webhooks/[0-9]{17,20}/[A-Za-z0-9_-]{50,} in
staged diffs and blocks. Sample one is in this repo at
scripts/git-hooks/pre-commit-discord-leak.sh.__repr__ / logging should never include URL. Python package masks
it; if you port to another language, do the same.On service startup, POST a harmless “service started” embed to a muted channel. If it fails, you know config is broken before the first real alert needs to fire.
# In your service init
try:
ok = sender.send_embed(
Channel.DEV,
build_embed(title="service-x started", description=str(version), color=0x808080),
)
if not ok:
log.warning("Discord notifier not reachable at boot")
except Exception:
log.exception("Discord notifier boot probe failed")
If your code uses the package in a class constructor (think:
OrderManager.__init__() building a real DiscordSender), test runs
will fire real messages unless you defend against it. Either:
DISCORD_WEBHOOK_* env vars + sets a
master NOTIFY_DISCORD_ENABLED=0 flag for the test sessionSample conftest in this repo at conftest.py of the parent project
(dhan-market-data).
When a P0 fires, reply to the embed inside Discord (right-click → Reply in Thread). The incident discussion stays attached to the alert, forever searchable. Better than Slack’s “where did we discuss that?”.
Webhooks can’t read messages, can’t respond to commands, can’t see reactions, can’t browse history. They are write-only. If you need two-way ChatOps (slash commands, button interactions), create a real Discord bot via the Developer Portal — much more setup, OAuth required.
Next: reference.