Verisco
Documentation

VERISCO Shield Monitor and respond

Webhooks

Premium plan

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#

HTTP
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
JSON
{
  "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:

Text
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.

Node
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();
});
Python
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.

↑↓ to move Enter to open Esc to close