Webhook
The webhook provider POSTs the deployment notification as JSON to any HTTP(S) endpoint whenever a deployment reaches a terminal state. Use it to fan out to systems without a dedicated provider — your own service, an automation runner, an incident tool’s inbound webhook, or a chat platform’s “incoming webhook” URL.
Configure
In the web app: Settings → Notifications → Add Integration, choose Webhook, then set
the events, an optional stack-label selector, the target URL, an optional signing secret, and
any extra headers. Or via the API — POST /api/v1/orgs/{org_id}/notification-integrations:
{
"name": "ops-webhook",
"provider_type": "webhook",
"enabled": true,
"events": ["deployment_succeeded", "deployment_failed", "deployment_cancelled"],
"stack_label_selector": { "environment": "prod" },
"provider_config": {},
"auth_config": {
"url": "https://hooks.example.com/gantrycd",
"secret": "optional-signing-secret",
"headers": { "Authorization": "Bearer <token>" }
}
}
events— which events to announce; see the event list.changes_only— whentrue, skip deployments whose plan reported no changes. A plan that could not be measured is still sent.auth_config.url— required,http(s). Stored encrypted (webhook URLs commonly embed a token), so it is never returned by the API.auth_config.secret— optional. When set, the body is signed (see below).auth_config.headers— optional extra request headers, e.g. anAuthorizationtoken.provider_configis unused for webhooks (send{}).
On update, omit auth_config (or leave the URL blank in the UI) to keep the stored URL,
secret, and headers.
Payload
The body is a JSON envelope describing what happened, to what, and where to look, plus one nested object carrying the detail for that kind of event. Every event uses the same envelope; only the detail object differs.
{
"event_type": "deployment_failed",
"org_id": "...",
"occurred_at": "2026-01-01T00:00:00Z",
"stack_id": "...",
"stack_name": "team-a-api",
"stack_labels": { "environment": "prod" },
"url": "https://gantrycd.example.com/<org>/stacks/<stack>/deployments/<id>",
"deployment": {
"deployment_id": "...",
"status": "failed",
"mode": "plan",
"commit_sha": "...",
"commit_message": "...",
"commit_author": "...",
"origin_type": "manual"
}
}
Read event_type first and the matching detail object second: deployment events carry a
deployment object, stack events carry a stack object, and stack_created /
stack_deleted carry neither because the envelope already says everything. Treat a missing
or unfamiliar detail object as “an event I do not handle” rather than an error — that is how
new event kinds arrive.
Stack events also populate actor_id and actor_kind (user or service_account) on the
envelope, using the same vocabulary as the audit log so the two can be correlated.
Fields inside scm (only scm_sync_failed): repository_url, inbox_event_type (the kind
of repository event that was given up on, not the notification’s own type), base_branch,
pr_number, last_error, and attempt_count. This event carries no stack_id.
Fields inside findings (only explore_findings_changed): current, and previous when
this integration has had a report before, each {cycles, orphans, duplicates, cycles_complete, orphans_complete}. previous is absent on the first report. When a
*_complete flag is false that read was over the analysis budget, so its count is unknown,
not zero — do not chart it as a drop.
Fields inside stack — all optional, and only two events set any:
| Field | On | Meaning |
|---|---|---|
previous_stack_id, previous_name | stack_renamed | What the stack was called before. The envelope carries the new identity. |
commit_sha, commit_message, commit_author | stack_synced | The commit the stack now tracks. |
Fields inside deployment that appear only on some events — treat every one as optional:
| Field | On | Meaning |
|---|---|---|
plan_summary | plan-style runs | Whether the plan had changes, and the add/change/destroy breakdown. Absent when nothing measured the plan. |
pr_number | origin_type: "pull_request" | The pull request the deployment previews. |
command_bytes | run_command_executed | How long the command was, in bytes. The command text is never sent — commands routinely contain secrets, so neither this nor the audit log records it. |
skipped_reason | deployment_auto_skipped, preview_auto_skipped | Why it was skipped — superseded, confirmation_timeout or queued_timeout for a deployment; always confirmation_timeout for a preview, the other preview retirements never notify. |
dependent_stack_id, trigger_error | dependent_trigger_failed | The stack that should have auto-deployed, and why it did not. |
Breaking change. The deployment fields used to sit at the top level, and the timestamp was
completed_at. They are now underdeploymentandoccurred_at. Existing receivers must be updated; there is no transition period.
Headers sent on every request: Content-Type: application/json,
User-Agent: GantryCD-Notifications, and X-GantryCD-Event: <event_type>, plus any headers
you configured.
Verifying signatures
When secret is set, GantryCD signs the raw request body with HMAC-SHA256 and sends
X-GantryCD-Signature-256: sha256=<hex> (the same scheme GitHub uses). Recompute the HMAC over
the body with your shared secret and compare in constant time; reject on mismatch.
Delivery semantics
Delivery is asynchronous with retries and dead-lettering, like every provider. A 4xx
response (except 429) is treated as a permanent failure and dead-lettered immediately;
429 and 5xx are retried with backoff. Use the Test button (or
POST .../notification-integrations/{id}/test) to send a sample — it bypasses the outbox, so the
HTTP response is the whole result and it never appears under
GET /api/v1/orgs/{org_id}/notification-deliveries. That endpoint lists only deliveries that are
in flight or have failed; successful ones are deleted on send.
Security note
The GantryCD backend makes the outbound request, so point integrations only at endpoints you trust. Redirects are not followed (the redirect response surfaces as a non-2xx failure). Use the signing secret so receivers can authenticate that a request really came from GantryCD.
For internals see Notifications internals.