Webhook payload
The JSON body, headers, event names and delivery rules of a webhook notification channel, payload shape version 1.
A webhook channel sends one HTTP POST per event to its URL. The body is JSON, shaped as version 1 below. The shape changes version only when a field is removed, renamed or changes meaning; a new field can appear without one, so ignore keys you do not know.
Quick reference
| Key | Type | Always present |
|---|---|---|
version | number | Yes. 1. |
id | string | Yes. The delivery id. |
event | string | Yes. |
kind | string | Yes. |
severity | string | Yes. |
title, body | string | Yes. body can be empty. |
url | string | Yes. |
test | boolean | Yes. |
createdAt | string | Yes. |
organization | object | Yes. |
project, environment, service, release, deployment, build, approval, cluster, alert | object or null | The key is always present; the value is null when the event does not concern it. |
Example
{
"version": 1,
"id": "6f1c2f0e-3f55-4a62-9a8e-7c8d1d0a1b2c",
"event": "deployment.failed",
"kind": "failure",
"severity": "error",
"title": "Release #7 of web failed to deploy to production",
"body": "Blocked on readiness. Probe failed. Open the deployment to see which stage stopped, then fix and redeploy.",
"url": "https://nebula.example.com/acme/deployments/0d9d4f58-1b1c-4f3a-8d0e-2f6b8e7a9c10",
"test": false,
"createdAt": "2026-03-14T14:12:41.204183Z",
"organization": { "id": "ORGANIZATION_ID", "slug": "acme" },
"project": { "id": "PROJECT_ID", "name": "Shop" },
"environment": { "id": "ENVIRONMENT_ID", "name": "production", "production": true },
"service": { "id": "SERVICE_ID", "name": "web" },
"release": { "id": "RELEASE_ID", "number": 7 },
"deployment": { "id": "DEPLOYMENT_ID" },
"build": null,
"approval": null,
"cluster": null,
"alert": null
}Top-level keys
id
The delivery id, a UUID. Every retry of one delivery carries the same id, so a receiver can ignore a repeat. The same value is in the X-Nebula-Delivery header.
event
What happened. One of:
| Event | Kind | Objects set |
|---|---|---|
approval.requested | approval | project, environment, service (if the approval names one), approval |
deployment.failed | failure | project, environment, service, release, deployment |
deployment.healthy | deploy | project, environment, service, release, deployment |
build.failed | failure | project, environment, service, build |
volume_backup.failed | failure | project, environment, service |
cluster.disconnected | warning | cluster |
cluster.agent_update_available | warning | cluster |
certificate.expiring | warning | project, environment, service |
alert.raised | alert | project, environment, service, alert |
alert.resolved | alert | project, environment, service, alert |
test | deploy | None. Sent by Send test; test is true. |
Which events reach a webhook follows the same rules as Slack: see See who is notified.
kind
The subscription the event belongs to: approval, failure, warning, deploy or alert. A channel receives the events whose kind it ticked under Events.
severity
info, warning, error or critical. Failures are error. A cluster losing contact, an expiring certificate and a warning-level alert are warning. A critical alert is critical. Approvals, healthy deployments, available agent updates, tests and recovered alerts are info.
title, body, url
The same text and console link a Slack message carries. url opens the event's target in the console.
createdAt
When the event was recorded, as an RFC 3339 timestamp in UTC. A retry keeps the original time.
Objects
| Key | Fields |
|---|---|
organization | id, slug |
project | id, name |
environment | id, name, production (boolean) |
service | id, name |
release | id, number |
deployment | id |
build | id |
approval | id |
cluster | id, name |
alert | id, rule |
alert.rule names the rule that fired, for example volume_usage, and stays stable across releases.
Request
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | NebulaCtrl-Webhook/1 |
X-Nebula-Event | The event value. |
X-Nebula-Delivery | The id value. |
| Your headers | Each header you set on the channel, with its stored value. |
Delivery
- The URL is dialled through the same address filter as Slack: a host that resolves only to private, loopback or link-local addresses is refused.
- Any
2xxstatus accepts the delivery. A redirect counts as a failure and is not followed. - An HTTP
4xxanswer other than408and429stops delivery at once. Anything else, and a timeout after 10 seconds, is retried with a wait that starts at 30 seconds and doubles to at most an hour, for up to 24 attempts. - The channel shows the last failure on Settings > Integrations, with the status code and the start of the response body.
- Deliveries for one channel are not ordered. Use
createdAtto order events.
Deployment states
Every state a deployment can be in and every blocker that can name why it is not moving, with what each means and what to do about it.
Limits
Every hard limit in NebulaCtrl, with its exact value and what is refused or clamped when you reach it. Covers API requests, streams, rate limits, sizes and counts in configuration files, shell sessions, notification channels, alerts, log search, retention periods and timeouts.