Overview
Overview
DisQ webhooks let you receive real-time vote and review events for your bot. You can choose either a Discord webhook (Discord-specific payload) or a custom webhook (JSON payload + HMAC signature).
Setup
You can configure webhooks from your bot dashboard:
- Open your bot page.
- Go to
Dashboard. - Open
Integrations. - Select
Webhooks. - Add your webhook URL, pick events, and save.
Event Types
VOTEREVIEW
Delivery
All webhooks are sent as HTTP POST requests with a JSON body.
Common Headers
Content-Type: application/jsonX-DisQ-Event: VOTE | REVIEWX-DisQ-Bot-Id: <bot-discord-id>User-Agent: DisQ/1.0
Custom Webhook Headers
Custom webhooks include authentication and signature headers:
Authorization: <bot-token>X-DisQ-Signature: t=<unix-seconds>,v1=<hex-hmac>
Custom Webhook Payload
{
"type": "vote",
"bot": {
"id": "1130656856990289961",
"name": "Example Bot"
},
"user": {
"id": "123456789012345678",
"username": "voter_name"
},
"timestamp": "2026-05-11T22:08:00.000Z",
"review": {
"rating": 5,
"content": "Great bot!"
}
}
Notes:
reviewis included only forREVIEWevents.typeis the lowercase event type (voteorreview).
Discord Webhook Payload
Discord webhooks follow Discord's standard payload format. You can customize:
contentusernameavatarUrlembed(title, description, color, fields)
Signature Verification (HMAC)
DisQ signs custom webhook requests using HMAC SHA-256 with your bot token. The signature is calculated from:
<timestamp>.<raw-json-body>
Where:
timestampis Unix time in seconds.raw-json-bodyis the exact JSON string sent in the request body.
Verification Steps
- Read the raw request body as a string (do not reformat JSON).
- Parse
X-DisQ-Signatureintotandv1. - Compute
HMAC_SHA256(secret, t + "." + rawBody). - Compare the computed hash to
v1using a constant-time comparison. - Reject requests with a timestamp older than 5 minutes.
Verification Example
import crypto from 'node:crypto'
function verifyDisqWebhook({ rawBody, signatureHeader, secret }) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((pair) => pair.split('='))
)
const timestamp = Number(parts.t)
const signature = parts.v1
if (!timestamp || !signature) return false
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - timestamp) > 300) return false
const payload = `${timestamp}.${rawBody}`
const digest = crypto.createHmac('sha256', secret).update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))
}
Cloudflare / WAF Tips
If your webhook endpoint is behind Cloudflare, do not require browser challenges on that path. Create a WAF rule to skip bot challenges for your webhook endpoint, for example:
- Match:
URI Path equals /webhook - Action:
SkipManaged Challenge / JS Challenge / Bot Fight Mode
Alternatively, host the webhook on a non-proxied subdomain.