DisQ
Webhooks

Overview

Receive vote and review events via custom or Discord webhooks.

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:

  1. Open your bot page.
  2. Go to Dashboard.
  3. Open Integrations.
  4. Select Webhooks.
  5. Add your webhook URL, pick events, and save.

Event Types

  • VOTE
  • REVIEW

Delivery

All webhooks are sent as HTTP POST requests with a JSON body.

Common Headers

  • Content-Type: application/json
  • X-DisQ-Event: VOTE | REVIEW
  • X-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:

  • review is included only for REVIEW events.
  • type is the lowercase event type (vote or review).

Discord Webhook Payload

Discord webhooks follow Discord's standard payload format. You can customize:

  • content
  • username
  • avatarUrl
  • embed (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:

  • timestamp is Unix time in seconds.
  • raw-json-body is the exact JSON string sent in the request body.

Verification Steps

  1. Read the raw request body as a string (do not reformat JSON).
  2. Parse X-DisQ-Signature into t and v1.
  3. Compute HMAC_SHA256(secret, t + "." + rawBody).
  4. Compare the computed hash to v1 using a constant-time comparison.
  5. 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: Skip Managed Challenge / JS Challenge / Bot Fight Mode

Alternatively, host the webhook on a non-proxied subdomain.