Webhook events
The JSON Countermind sends to your webhook, and how to verify that it came from Countermind.
Delivery
- One HTTPS
POSTper event, withContent-Type: application/json. - Your endpoint should answer with a 2xx status within 5 seconds.
- Failed deliveries are not retried. The integration shows the last status, so you can spot problems.
Payloads
{
"event": "finding.published",
"title": "Merchants can read each other's settlements",
"publicId": "ZF-0001-01",
"severity": "critical"
}{
"event": "scan.completed",
"number": 5,
"asset": "Merchant dashboard",
"findings": 3
}{
"event": "retest.published",
"publicId": "ZF-0001-02",
"result": "fixed"
}result is fixed or not_fixed. Send test delivers a finding.published event with the ID ZF-TEST.
Verify the signature
When the webhook has a signing secret, each request carries an x-countermind-signature header: the lowercase hex HMAC-SHA256 of the raw request body, keyed with your secret. Compute it over the exact bytes you received and compare in constant time.
import crypto from 'node:crypto';
export function isFromCountermind(rawBody, header, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(header || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib, hmac
def is_from_countermind(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")