VERISCO Shield Monitor and respond
Webhooks
Security events from your tenant, posted to an address of yours as they happen, signed so you can prove they came from VERISCO.
Create a webhook on Webhooks in your console: an HTTPS address, the events you want, and a description. Each webhook has its own signing secret.
Events#
| Event | Sent when |
|---|---|
alert.triggered |
A threshold alert you configured has tripped |
credentials.updated |
Database credentials changed |
ip.block.lifted |
An IP block was lifted |
licence.changed |
Licence state changed |
monitoring.digest |
Weekly summary of what your policy decided |
policy.device.updated |
Device policy changed |
policy.lockout.updated |
Lockout policy changed |
policy.mfa.updated |
Multi-factor policy changed |
policy.network.updated |
Network policy changed |
policy.password.updated |
Password policy changed |
policy.session.updated |
Session policy changed |
session.revoked |
Sessions were revoked for a user |
session.terminated |
A session was terminated |
signin.denied |
A sign-in was refused by policy |
signin.step_up |
A sign-in required additional verification |
tenant.activated |
This tenant was reactivated |
tenant.suspended |
This tenant was suspended |
The request#
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: VERISCO-Webhooks/1
X-Verisco-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Verisco-Event: session.terminated
X-Verisco-Delivery: 3f6c1d0e8a7b4c2d9e5f0a1b2c3d4e5f{
"id": "3f6c1d0e8a7b4c2d9e5f0a1b2c3d4e5f",
"event": "session.terminated",
"tenant_id": "your-tenant-id",
"created_at": "2026-10-04T09:12:44Z",
"data": {}
}| Field | Meaning |
|---|---|
id |
Unique per delivery. The same value as X-Verisco-Delivery. |
event |
One of the event names above. |
tenant_id |
Your tenant. |
created_at |
When the event happened, in UTC. |
data |
The details of the event. Its fields depend on the event. |
Verify the signature#
X-Verisco-Signature has the form t=<unix time>,v1=<hex>. The signature is an HMAC-SHA256, keyed with your webhook's secret, over the timestamp, a dot, and the raw request body:
v1 = HMAC_SHA256(secret, "<t>." + body)The timestamp is inside the signed material, so it cannot be altered to make an old body look fresh. Reject anything older than a few minutes and you have replay protection as well.
const crypto = require('crypto');
function verify(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.t || !parts.v1 || age > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: the body must be the raw bytes, not the parsed object.
app.post('/hooks/verisco', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(process.env.VERISCO_WEBHOOK_SECRET, req.get('X-Verisco-Signature') || '', req.body)) {
return res.status(400).end();
}
const event = JSON.parse(req.body);
res.status(204).end();
});import hashlib
import hmac
import time
def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
if "t" not in parts or "v1" not in parts:
return False
if abs(time.time() - int(parts["t"])) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Sign the raw body
Verify the bytes you received, before any JSON parsing. A parsed and re-serialised body can differ by a space, and the signature will not match.
Delivery#
| Success | Any 2xx response. |
| Timeout | 8 seconds. |
| Retries | Up to 6 attempts over about two hours, with growing gaps. |
| Redirects | Not followed. |
| Guarantee | At least once. The same delivery can arrive twice. |
Because delivery is at-least-once, make your receiver idempotent: keep the X-Verisco-Delivery values you have processed and ignore a repeat.
Respond quickly and do the work afterwards. A receiver that takes longer than the timeout is recorded as a failure and retried, even if it eventually succeeded.
Requirements for your endpoint#
- HTTPS.
- A publicly reachable hostname. Addresses on private networks are refused.
Testing#
Use the test action on the Webhooks page. It posts a delivery through the same path as a real event, so a signature your code accepts in the test is one it will accept for real.
When your plan changes#
On a plan without webhooks, deliveries stop. They are not queued and are not replayed when the feature returns.
Something missing or wrong on this page? Tell us and we will fix it.