Outgoing webhooks
Every event the platform can send, the signed payload, retries, and the SSRF rules every delivery passes through.
A webhook is a URL the platform calls when something happens. Anything the UI would show a person can be delivered: ticket changes, SLA transitions, service status, backups, and every in-app notification. Deliveries are signed, retried, logged for 30 days, and refused to any address that looks like the inside of your network unless you say otherwise.
Webhooks are created on the Integrations page or with the createWebhook mutation, by an admin.
Events
| Family | Events |
|---|---|
| ticket | ticket.created, ticket.updated, ticket.status_changed, ticket.assigned, ticket.commented, ticket.resolved, ticket.closed, ticket.deleted |
| workflow | workflow.started, workflow.completed, workflow.failed, workflow.step_completed |
| sla | sla.breached, sla.at_risk, sla.met, sla.daily_report |
| service | service.down, service.up, service.degraded |
| backup | backup.success, backup.failed |
| user | user.created, user.updated |
| notification | notification.created, one per in-app notification, with the notification's own type in the payload |
webhookEventTypes returns the list the running version supports.
The payload
POST https://hooks.example.com/itops
Content-Type: application/json
X-ITOps-Signature: sha256=3f1a…
{
"id": "a4c1…",
"event": "service.down",
"timestamp": "2026-09-11T13:30:07Z",
"data": {
"service": { "id": "…", "name": "notification-service", "path": "meridian/commerce/prod/eu-central-1/notification-service" },
"previousStatus": "OPERATIONAL", "status": "DOWN", "message": "0/2 replicas ready"
}
}
data is the object the event is about, in the same shape GraphQL returns it. customPayloadTemplate on the webhook replaces the body with your own template for receivers that want a specific format, such as a chat message.
Signature
When the webhook has a secret, the body is signed with HMAC-SHA256 and the hex digest is sent as X-ITOps-Signature: sha256=<hex>. Verify it before trusting the body:
echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET"
Configuration
| Field | Meaning |
|---|---|
url, method |
the target; POST unless you say otherwise |
secret |
enables the signature |
headers |
extra headers, for a bearer token the receiver expects |
events |
which of the above to deliver |
filters |
narrow by attributes of the event, for example one SLA group or one ticket priority |
timeoutMs |
per attempt |
retryMax, retryDelayMs, retryBackoff |
how many times and how patiently to retry a non-2xx or a timeout |
status |
ACTIVE, INACTIVE, or ERROR once retries are exhausted repeatedly |
customPayloadTemplate |
replace the default body |
testWebhook sends a synthetic event immediately. webhookExecutions lists every attempt with status code, duration and response excerpt for 30 days; webhookStats summarises them.
What the platform refuses to call
Every delivery, and every redirect it is sent through, passes the URL validator:
- only
httpandhttps;file://,gopher://,ldap://and the rest are rejected; - the host is resolved and every address it resolves to is checked: loopback, private ranges (10/8, 172.16/12, 192.168/16), link-local, carrier-grade NAT, multicast and unspecified addresses are refused, and the cloud metadata address
169.254.169.254is refused by name as well; - a DNS failure is a refusal, not a retry;
- redirects are followed at most five times and each hop is validated again, so a public URL that bounces to an internal one is caught.
This closes the classic server-side request forgery hole where a webhook is pointed at the cluster's own metadata service or an internal admin port. Two settings loosen it, deliberately:
| Setting | Effect |
|---|---|
ITOPS_SECURITY_WEBHOOK_HOST_ALLOWLIST=hooks.example.com,alerts.internal |
only these hosts may be called, and private addresses are allowed for them |
ITOPS_SECURITY_ALLOW_PRIVATE_WEBHOOKS=true |
allow any private address. Development only. |
A receiver inside the cluster is therefore an allowlist entry plus, if the network policy is on, an egress rule for its namespace.
Receiving in Slack, Teams or a chat tool
Point the webhook at the incoming-webhook URL the chat tool gives you and use customPayloadTemplate to produce the message shape it expects. The notification.created event is the convenient one for this: it fires for everything a person would have been notified about in the UI, with the human-readable text already composed.