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.Open Settings → Webhooks and add your HTTPS endpoint URL.
- 2.Save the secret shown in the one-time reveal — you'll use it to verify signatures. It's not shown again.
- 3.Click the Test button to fire a sample event and confirm your handler is correct.
- 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-Signature | HMAC-SHA256 of the raw request body, prefixed with sha256=. |
| X-CQwerty-Event | Event name. Currently always scan.complete. |
| X-CQwerty-Test | Set to true when fired from the Test button. |
| User-Agent | Always 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.