WEBHOOKS

Receive scan events in real time.

CQwerty Shield POSTs a JSON event to your endpoint every time a monitored scan completes. HMAC signed, retries on failure, and you can verify deliveries from the dashboard.

Quick start

  1. 1.Open Settings → Webhooks and add your HTTPS endpoint URL.
  2. 2.Save the secret shown in the one-time reveal — you'll use it to verify signatures. It's not shown again.
  3. 3.Click the Test button to fire a sample event and confirm your handler is correct.
  4. 4.Inspect the View log button to see every delivery, status code, and response snippet.

Event payload

scan.complete

{
  "event": "scan.complete",
  "job_id": "a1b2c3d4-5678-90ef-abcd-1234567890ab",
  "data": {
    "domain": "example.com",
    "ssl_grade": "A",
    "email_grade": "B",
    "headers_grade": "A",
    "overall_score": 86,
    "risk_level": "LOW",
    "issues_count": 3,
    "scanned_at": "2026-04-12T10:23:14Z"
  }
}

Slack webhook URLs (hooks.slack.com) and Discord webhook URLs (discord.com/api/webhooks) automatically receive their native Block Kit / Embed payloads instead of the raw JSON above.

Request headers

X-CQwerty-SignatureHMAC-SHA256 of the raw request body, prefixed with sha256=.
X-CQwerty-EventEvent name. Currently always scan.complete.
X-CQwerty-TestSet to true when fired from the Test button.
User-AgentAlways CQwerty-Shield-Webhook/1.0.

Verify the signature

typescript

// Express + crypto example
import express from 'express'
import crypto from 'crypto'

const app = express()
app.use(express.raw({ type: 'application/json' }))

const SECRET = process.env.CQWERTY_WEBHOOK_SECRET // from /dashboard/settings

app.post('/cqwerty-webhook', (req, res) => {
  const sig = req.header('X-CQwerty-Signature') || ''
  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(req.body)
    .digest('hex')

  const sigBuf = Buffer.from(sig)
  const expBuf = Buffer.from(expected)
  if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
    return res.status(401).send('Invalid signature')
  }

  const event = JSON.parse(req.body.toString())
  console.log('CQwerty event:', event)
  // -> { event: 'scan.complete', data: { domain, ssl_grade, ... } }

  res.sendStatus(200)
})

Best practices

  • Always verify signatures. Use a constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest, etc.) to avoid timing attacks.
  • Respond fast. Return 200 within 10 seconds. Push slow work into a background queue.
  • Be idempotent. Retries can cause duplicate deliveries. Dedupe by job_id + scanned_at.
  • Use HTTPS. Plain HTTP endpoints are rejected at registration time.
  • Inspect the delivery log. The View log button in Settings shows every recent attempt with status codes and response snippets — use it to debug.
Add a webhook now →