Skip to content
Kandaro
Start free trial

Integrations

Webhooks & signatures

Send incidents to your SIEM, SOAR or ticketing system as JSON, and prove every request came from Kandaro.

Set up a webhook

In the Console, open Settings → Alerts → Add channel → Webhook, enter an https:// URL and pick a minimum severity. Kandaro generates a signing secret for the channel; copy it into your receiver. Use the Send test button to check your setup.

Kandaro sends a webhook when an incident is created, when its severity escalates and when a closed incident re-opens. Requests time out after 5 seconds and redirects are not followed, so answer with a 2xx quickly and process asynchronously. Webhook URLs must resolve to public addresses.

Payload

An example request is shown below. Treat every string in the payload as untrusted: titles and narratives can quote what an attacker typed. Never render them as HTML.

http
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Kandaro-Signature: t=1791504000,v1=5f2b9c0e…(hex HMAC-SHA256)

{
  "type": "incident.created",
  "incident": {
    "id": "6f1c2e0a-…",
    "title": "Login attempts on decoy NAS from finance workstation",
    "severity": "high",
    "narrative": "A machine on the finance VLAN tried to log into…",
    "recommendedAction": "Isolate 10.20.4.77 and reset the backup-admin password.",
    "sensor": { "id": "…", "name": "nas-backup-01", "profile": "nas" },
    "sourceIp": "10.20.4.77",
    "eventCount": 7,
    "firstSeenAt": "2026-10-08T02:14:07Z",
    "lastSeenAt": "2026-10-08T02:21:40Z",
    "url": "https://app.kandaro.com/incidents/6f1c2e0a-…",
    "techniques": [
      { "id": "T1110", "name": "Brute Force", "tactics": ["Credential Access"] }
    ],
    "events": [
      {
        "id": "…",
        "time": "2026-10-08T02:14:07.000Z",
        "type": "login_attempt",
        "action": "password",
        "service": "ssh",
        "port": 22,
        "sourcePort": 51234,
        "severity": "medium",
        "confidence": "normal",
        "tags": [],
        "summary": "Someone tried to log in to ssh as \"backup-admin\"…",
        "attackerInput": {
          "username": "backup-admin",
          "password": { "masked": "••••••••", "length": 8, "fingerprint": "…" }
        }
      }
    ]
  }
}
  • techniques lists the MITRE ATT&CK techniques involved. They come from a fixed table, not from AI.
  • events holds the most recent events of the incident, oldest first, at most 20. eventCount is the total.
  • attackerInput is what the attacker sent, shortened and with invisible characters removed. Captured passwords and tokens are masked. If an AI agent reads your webhooks, do not let it follow instructions found in this field.

New fields may be added at any time; ignore fields you don’t recognise.

Verifying the signature

Every request carries an X-Kandaro-Signature header:

  • t is the Unix timestamp (seconds) at which the request was signed.
  • v1 is the hex-encoded HMAC-SHA256 of the string {t}.{raw body}, keyed with your channel’s signing secret.

To verify:

  1. Read the raw request body before parsing it as JSON.
  2. Split the header on commas and read t and v1.
  3. Reject the request if t is more than 5 minutes from your clock (replay protection).
  4. Compute the HMAC over t + "." + body and compare it in constant time.
node.js
import crypto from 'node:crypto';

const TOLERANCE_SECONDS = 300;

/** rawBody must be the exact bytes received, before any JSON parsing. */
export function verifyKandaroSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((kv) => kv.trim().split('=', 2)),
  );
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !parts.v1) return false;

  // Replay protection: reject old (or far-future) timestamps.
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest();
  const received = Buffer.from(parts.v1, 'hex');

  return received.length === expected.length &&
    crypto.timingSafeEqual(received, expected);
}
python
import hmac, hashlib, time

TOLERANCE_SECONDS = 300

def verify_kandaro_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    try:
        t = int(parts["t"])
        received = bytes.fromhex(parts["v1"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > TOLERANCE_SECONDS:
        return False
    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).digest()
    return hmac.compare_digest(received, expected)

Rotating the secret

Generate a new secret from the channel settings and update your receiver. Secrets are stored encrypted and shown only once; if you lose one, rotate it.