Gateway to Worker
Allowed Discord events
Websocket event → channel and event checks → unneeded data removed → signed HMAC request with a timestamp → Worker verification → one-time nonce claimed in D1 → workflow handler.
How the Discord Worker and gateway handle commands, live events, and duplicate requests.
Discord uses two services. The Worker handles short, signed HTTP requests. The gateway keeps a websocket open for live Discord events and the bot’s status.
Club rules stay in the API. Discord actions stay in the Worker. The gateway only handles work that needs a live Discord connection.
System flow
Discord interactions, API events, and Gateway events arrive on separate routes. The Worker checks each message. It also claims the nonce for internal events.
A slash command
An API event
A Gateway event
A presence update
The Worker receives Discord interactions, events from the API, events forwarded by the gateway, and health checks. After it verifies a request, it turns that request into a Discord REST action.
It can:
The Worker does not keep a Discord websocket open. It checks the request’s signature or internal identity, finishes the HTTP work, and stops.
The gateway is a Node.js and discord.js process that runs outside Cloudflare Workers. It keeps Discord’s gateway websocket open.
It does not forward every event. It checks the configured channel allowlists and event filters, removes data the Worker does not need, and signs the request before sending it. This keeps traffic low and limits the Discord data sent to the Worker.
It also controls the bot’s presence. The Worker calls a private /presence endpoint through a Cloudflare VPC service. The gateway verifies the request, then uses its live discord.js client to update the bot’s status.
Both directions use HMAC, a signature made with a shared secret. It lets the receiver check that a trusted service sent the request and that the content did not change.
Data moves in two directions:
Gateway to Worker
Websocket event → channel and event checks → unneeded data removed → signed HMAC request with a timestamp → Worker verification → one-time nonce claimed in D1 → workflow handler.
Worker to gateway
Worker service → Cloudflare VPC binding → gateway /presence route → HMAC check → discord.js status update.
Slash commands and other Discord interactions arrive as signed HTTP requests. Discord signs the exact raw request body, so the Worker checks the signature before it parses the request.
The Worker:
This lets the Worker answer Discord on time while the API stays in charge of loans, memberships, studio requests, film requests, schedules, and accounts.
The current interaction check proves that Discord sent the request. It does not check how old the timestamp is or claim a nonce in D1. The next two paths do both in the application code to block repeated requests.
The API sends an internal event when a saved club change also needs an action in Discord.
The event has a type, payload, timestamp, and nonce. A nonce is a one-time ID for that event. The API also creates an HMAC signature. The Discord Worker checks the signature and timestamp, then asks the API to claim the nonce in D1.
The Worker parses and sends the event only after the nonce claim succeeds.
Service-binding requests may be sent again when a response is lost or unclear. Without a shared nonce claim, one event could create two channels, send two DMs, or add the same role twice. Once D1 accepts a nonce, it rejects that nonce if it appears again.
The signature shows who sent the event. The nonce stops the same event from running twice.
Each known event type must have the fields its service expects. There is one narrow fallback for a generic message: an unknown event may continue only if it has valid message content or embeds. The Worker rejects anything that matches neither a known event type nor that fallback.
Some member, message, and reaction events are available only through the websocket.
The VPS gateway receives those events first. It checks the event and channel against its allowlists, removes fields the Worker does not need, and creates a timestamped HMAC request with a nonce.
The Worker then:
The allowlist lives in the gateway’s environment settings. The websocket and Worker can both be healthy while a channel is missing from that list. If a reaction or message workflow stops working, check the forwarding channel IDs early.
A staff member approves a request in the dashboard:
If step 6 finds a duplicate, the D1 approval stays saved, but the Discord action does not run again. If Discord REST fails after D1 saves the approval, the system logs enough detail to retry or repair the Discord action without creating another approval.
For a command, the request moves the other way:
Because both paths use the same API change, the website and Discord cannot create separate versions of a loan, request, or review flow.
Each incoming path checks the sender, repeated requests, and allowed data:
| Path | Sender check | Repeat-request check | Allowed data |
|---|---|---|---|
| Discord interaction | Discord Ed25519 signature over the timestamp and raw body | The current check has no local age window or nonce claim | Known interaction and command format |
| API internal event | Shared HMAC signature | Timestamp window and D1 nonce claim | Known internal-event type and fields |
| Gateway event | Shared HMAC signature | Timestamp window and D1 nonce claim | Gateway allowlist, data cleanup, and known event type |
| Bot presence | Private VPC route and HMAC | Timestamped request check | Limited /presence request format |
Secrets stay in the Worker or VPS environment settings. They are not in the website bundle, repository, or event payload.
The Worker logs details when it rejects a signature or nonce, receives invalid event data, or gets an API or Discord REST error. It removes common credential fields before writing the log.
The gateway has its own health checks and reconnect logic because its websocket can disconnect while the Worker stays healthy. It is deployed and restarted separately from the Cloudflare Worker.
The system does not have end-to-end tracing. It has structured logs, health endpoints, and Cloudflare observability samples. To follow one failure across the API, Worker, gateway, and Discord, you may need to match timestamps, event names, and safe IDs across several logs.