Neu: Freigabe von Agenten, Red Teaming und Modell-Scans. Mehr erfahren
MITRITY
Docs / Integrationen / Notification Webhooks

Notification Webhooks

A notification webhook sends MITRITY's notifications to an endpoint of yours, such as a ticketing system, a chat tool or an automation platform. Each notification arrives as one JSON POST, signed so your endpoint can check that it came from MITRITY. These are the same notifications your team sees in the bell, in Slack and by email, delivered to a system instead of a person.

Notification webhooks are part of the Pro and Enterprise plans. They are not SIEM forwarding, which sends your governance events (agent decisions and the rest of the audit trail) to a SIEM, and is part of the Enterprise plan.

What each plan gets

StarterProEnterprise
Notification webhooks—✓✓
SIEM forwarding (Splunk, webhook, CEF, syslog)——✓
  • Pro sends notifications to up to 10 notification webhooks.
  • Enterprise gets the same, and SIEM forwarding. Its SIEM destinations also receive the notifications (see Notification events).
  • Starter has neither webhooks nor Slack. Notifications reach the bell and email.

What a webhook receives

A webhook receives every notification whose Webhook column is on under Settings → Notifications → Alert routing. These notifications reach webhooks by default:

KindWhen it fires
credentials.suspendedAn agent's credential access was suspended, by its drift score or by a person
credentials.restoredA person restored an agent's credential access
governance.review_resolvedA governance review of an agent was approved, attested, sent back for changes or denied
redteam.findingAn adversarial testing run found that a guardrail did not stop something it is configured to stop
report.readyA compliance report finished generating and is on file

You can turn on any other notification for webhooks in the same column, such as held actions waiting for approval or model risk findings. The defaults apply until you change them; you don't need to save the notification settings first.

A notification webhook never receives:

  • your governance events (agent decisions, DLP matches, SaaS activity and admin changes): that is SIEM forwarding;
  • AIBOM drift advisories, which reach the bell, Slack and email, and SIEM destinations on the Enterprise plan.

Set up a webhook

Owners and Managers manage webhooks. Every role can see them.

  1. Open Settings → Notifications, and go to Webhooks.
  2. Choose Add webhook, and enter a name and the endpoint URL. The URL must use https:// and be reachable from the internet.
  3. Optional: add an Authorization header, for example Bearer followed by your endpoint's token.
  4. Leave Sign every request on. After you save, MITRITY shows the signing secret once. Copy it to your endpoint, which uses it to check each request.
  5. Choose Send test. MITRITY posts a test notification right away and shows whether your endpoint accepted it.
  6. Under Alert routing, check which notifications have the Webhook column on.

Each webhook shows its last delivery, or the error of its last failed one.

The request

Each notification is one POST to each webhook, with the notification as a JSON body:

{
  "type": "report.ready",
  "version": "1.0",
  "event_id": "7b1c9a7e-8f2d-4a31-9c77-1d2e3f4a5b6c",
  "tenant": { "id": "b2c3d4e5-6f70-4812-9a3b-4c5d6e7f8091" },
  "severity": "info",
  "title": "Report ready: Q3 evidence",
  "body": "[MITRITY] Report ready\n\nReport: Q3 evidence (SOC 2 compliance report)\nRecords cited: 1204\n…",
  "link_url": "/app/reports?report_type=soc2&highlight=9d8c7b6a-5f4e-4d3c-2b1a-0f9e8d7c6b5a",
  "generated_at": "2026-10-21T08:00:00Z",
  "payload": {
    "report_id": "9d8c7b6a-5f4e-4d3c-2b1a-0f9e8d7c6b5a",
    "report_type": "soc2",
    "evidence_count": 1204
  }
}
FieldDescription
typeThe kind of notification, such as report.ready. A test sends notification_webhook.test
versionThe envelope version. It stays 1.0 when a field is added, and changes when one is removed or renamed
event_idThe notification's ID. It is the same for every webhook and SIEM destination that receives it, so use it to deduplicate
tenant.idYour organization. Nothing else about it is sent
severityinfo, warning or critical
title, body, link_urlWhat a person reads in the dashboard, and the dashboard path of what it is about, so a receiver that knows nothing about a kind can still file it
generated_atWhen MITRITY sent it, in RFC 3339
payloadThe kind's own data. It never carries a credential, a prompt or a matched value

Headers:

HeaderValue
Content-Typeapplication/json
User-Agentmitrity-notifications/1.0
X-Mitrity-EventThe notification's type
X-Mitrity-Event-IDThe notification's event_id
X-Mitrity-Webhook-IdThe ID of the webhook the request is for
X-Mitrity-Signaturet=<unix seconds>,v1=<hex>, when the webhook signs requests
AuthorizationThe webhook's Authorization header, when you set one

Verifying signatures

When a webhook signs requests, every request carries X-Mitrity-Signature:

  • t is the time of sending, in seconds since the epoch;
  • v1 is HMAC-SHA256 of t, a dot and the exact bytes of the body, keyed with the signing secret, in lowercase hex.

To check a request, your endpoint:

  1. reads t and every v1 from the header, and accepts the request if any v1 matches, so the check keeps working if a later version sends two while a secret is being changed;
  2. computes the expected value with its copy of the secret, over the raw body as received, and compares it in constant time;
  3. refuses the request when t is more than 300 seconds from its own clock, which it keeps in sync with NTP;
  4. deduplicates on event_id.

Use the raw body: parsing the JSON and serializing it again changes the bytes. SIEM webhook destinations are signed the same way, so one check serves both.

In Python:

import hashlib
import hmac
import re
import time

TOLERANCE_SECONDS = 300


def verify_signature(secret: bytes, header: str, body: bytes) -> bool:
    """True when the X-Mitrity-Signature header matches the raw request body."""
    timestamp = None
    signatures = []
    for item in header.split(","):
        key, _, value = item.strip().partition("=")
        if key == "t":
            timestamp = value
        # Only a well-formed value can match: anything else is skipped, never compared.
        elif key == "v1" and re.fullmatch(r"[0-9a-f]{64}", value):
            signatures.append(value)
    if timestamp is None or not signatures or not re.fullmatch(r"[0-9]+", timestamp):
        return False
    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    expected = hmac.new(secret, timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, signature) for signature in signatures)

In Node.js:

import crypto from 'node:crypto'

const TOLERANCE_SECONDS = 300

// True when the X-Mitrity-Signature header matches the raw request body (a Buffer).
export function verifySignature(secret, header, rawBody) {
  let timestamp
  const signatures = []
  for (const item of header.split(',')) {
    const [key, ...rest] = item.trim().split('=')
    const value = rest.join('=')
    if (key === 't') timestamp = value
    // Only a well-formed value can match: anything else is skipped, never compared.
    else if (key === 'v1' && /^[0-9a-f]{64}$/.test(value)) signatures.push(value)
  }
  if (!timestamp || !/^[0-9]+$/.test(timestamp) || signatures.length === 0) return false
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex')
  // Both are 64 bytes here, so timingSafeEqual cannot throw.
  return signatures.some((signature) =>
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)),
  )
}

To change the secret, choose Generate a new secret on the webhook. The new secret is used from the next notification, so update your endpoint right away.

Delivery

  • Each notification gets one attempt per webhook, with a 10-second timeout. A failed delivery is not retried: the bell keeps every notification, and the webhook shows the error.
  • Only a 2xx response counts as delivered. A redirect is not followed and counts as a failure, so configure the final URL.
  • A slow or failing webhook does not delay the others, or the notification's other channels.
  • The same notification can arrive at a webhook and at a SIEM destination pointed at the same endpoint. Deduplicate on event_id.

Security

  • Public addresses only. MITRITY delivers only to hosts whose addresses are public. It refuses private, loopback, link-local and other reserved ranges, and cloud metadata hosts, when you save the webhook and again on the address it connects to at every delivery, so a name that later resolves to a private address is refused.
  • TLS is always verified, with TLS 1.2 or later.
  • Secrets stay secret. The Authorization header and the signing secret are stored encrypted and are never returned, logged or written to the audit trail. A saved Authorization header is never sent to a new endpoint: when you change a webhook's URL, enter the header again, or confirm that the new endpoint needs none.
  • No fixed source address. Deliveries do not come from a fixed set of IP addresses, so authenticate them with the signature or the Authorization header rather than an address allowlist.
  • Audit. Adding, changing, testing and deleting a webhook is recorded on the audit trail as notification_webhook.created, notification_webhook.updated, notification_webhook.tested and notification_webhook.deleted, with the endpoint's host, never the full URL.

When your plan changes

  • Starter → Pro: you can add webhooks.
  • Enterprise → Pro: your webhooks keep working. SIEM forwarding is part of Enterprise, so your SIEM destinations are kept but can't be changed: send the notifications you rely on to a webhook.
  • Pro → Starter: your webhooks and their settings are kept, but nothing is sent to them, and they can't be changed or tested. You can still delete them. After an upgrade, delivery picks up with new notifications.

While your account is suspended, or your Enterprise contract is in its grace period, nothing is sent to your webhooks, whatever your plan, as on every other channel. They are kept, and delivery picks up with the next notification after the suspension is lifted or the contract is renewed (Billing & Plans).

API

MethodEndpointDescription
GET/api/v1/notification-webhooksList your webhooks, with plan_eligible (whether your plan allows them) and max_webhooks
POST/api/v1/notification-webhooksAdd a webhook
GET/api/v1/notification-webhooks/{id}Get a webhook
PUT/api/v1/notification-webhooks/{id}Change a webhook
DELETE/api/v1/notification-webhooks/{id}Delete a webhook
POST/api/v1/notification-webhooks/{id}/testSend a test notification now, and get the outcome
curl -X POST https://api.mitrity.com/api/v1/notification-webhooks \
  -H "Authorization: Bearer mk_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ticketing intake",
    "endpoint_url": "https://hooks.example.com/mitrity",
    "signing_secret": "a-random-secret-of-at-least-32-characters",
    "enabled": true
  }'
FieldDescription
nameA name for the webhook, up to 255 characters
endpoint_urlAn https:// URL, up to 2048 characters, with no user name or password in it
auth_headerOptional. Sent as the Authorization header, up to 4096 characters of plain text
signing_secretOptional. The secret requests are signed with, 32 to 256 characters. Generate a random one
enabledWhether the webhook receives notifications. true by default

A response never includes a secret: it says has_auth_header and has_signing_secret. It also has last_sent_at, and last_error with last_error_at for the last failed delivery.

A PUT sends name and endpoint_url. enabled left out keeps its value. Each secret has three states: leave it out (or send null) to keep it, send an empty string to remove it, or send a new value to replace it. A PUT that changes endpoint_url while an Authorization header is saved must send auth_header again, or an empty string, and is refused with 400 otherwise.

The test answers delivered, its event_id, the HTTP status_code (null when the endpoint did not answer), the error and duration_ms. A test does not change the webhook's last delivery. Each webhook takes one test every 10 seconds; another one sooner is refused with 429.

Errors:

  • 400: a field is invalid, and a fields array names each one, for example an endpoint whose host resolves to a private address;
  • 402 with the code plan_upgrade_required: adding, changing and testing webhooks need the Pro or Enterprise plan. Listing and deleting work on every plan;
  • 403: only Owners and Managers change webhooks;
  • 409 with the code webhook_limit_reached: an organization can have 10 webhooks.

Troubleshooting

  • Nothing arrives: send a test. If the test is delivered, check that the Webhook column is on for the notifications you expect under Alert routing.
  • destination returned HTTP 401 or 403: check the Authorization header, or that your endpoint has the current signing secret.
  • endpoint redirected: configure the final URL. Redirects are not followed.
  • destination URL blocked by security policy: the host resolves to an address MITRITY does not deliver to, such as a private one.
  • Duplicates: deduplicate on event_id. It repeats when the same notification reaches several webhooks or SIEM destinations pointed at one endpoint.