Skip to content
GantryCD

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 — when true, 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.secretoptional. When set, the body is signed (see below).
  • auth_config.headersoptional extra request headers, e.g. an Authorization token.
  • provider_config is 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:

FieldOnMeaning
previous_stack_id, previous_namestack_renamedWhat the stack was called before. The envelope carries the new identity.
commit_sha, commit_message, commit_authorstack_syncedThe commit the stack now tracks.

Fields inside deployment that appear only on some events — treat every one as optional:

FieldOnMeaning
plan_summaryplan-style runsWhether the plan had changes, and the add/change/destroy breakdown. Absent when nothing measured the plan.
pr_numberorigin_type: "pull_request"The pull request the deployment previews.
command_bytesrun_command_executedHow 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_reasondeployment_auto_skipped, preview_auto_skippedWhy 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_errordependent_trigger_failedThe 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 under deployment and occurred_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.