Notifications
GantryCD tells your other systems when deployments run. Notification integrations are per-organization configurations that subscribe to deployment-execution events and deliver a message to a provider — Slack today, more later.
| Provider | What you configure | How it’s picked up |
|---|---|---|
| Slack | A bot token (your own Slack App install) + a default channel | Each matching org integration receives a chat.postMessage per subscribed event. |
| Webhook | An HTTP(S) URL, optional signing secret + headers | Each matching org integration receives a signed JSON POST per subscribed event. |
The model
-
Per organization. Every org defines its own integrations and supplies its own credentials (e.g. its own Slack bot token). There is no operator-level, shared token — the provider code is built into the backend, but the secrets belong to each org and are encrypted at rest.
-
Event subscriptions. An integration lists the events it wants. The first family concerns deployments — plan, destroy, refresh and raw executions that take the stack’s lane. Pull-request previews and local plans are not deployments; they have their own family below.
Event Fires when deployment_succeededA deployment finished. deployment_failedA deployment failed. deployment_cancelledA deployment was cancelled. deployment_auto_skippedThe stack’s queue policy skipped a deployment on its own — superseded by a newer one, or aged out waiting for confirmation or a free lane. A skip you perform stays silent. deployment_awaiting_confirmationA deployment has run its plan and is waiting for someone to approve the apply. run_command_executedSomeone ran an arbitrary command against a stack. That runs code on a runner holding the stack’s cloud credentials, so it is worth watching. The message reports the command’s size, never its text — commands often contain secrets. pull_request_promotedSomeone promoted a pull request’s plan to a real deployment. dependent_trigger_failedAn apply succeeded and should have auto-deployed a stack that depends on it, but could not (the dependent was deleted, is unsynced, failed a preflight check, …). Without this the broken automation is only visible on the parent deployment’s page. Previews. A preview is a read-only plan that never takes the stack’s lane: every push to an open pull request produces one, and so does a local plan. They mirror the deployment events one for one, and are separate so that subscribing to deployment outcomes does not drown you in one message per push.
Event Fires when preview_succeededA preview’s plan finished. preview_failedA preview’s plan failed. preview_cancelledA preview was cancelled. preview_auto_skippedA preview waiting for confirmation aged out under the stack’s confirmation timeout. The routine retirements — superseded by a newer push, retracted because the stack’s plan requirements stopped being met, or the pull request closed — stay silent: they are the pull request moving on, not something to act on. preview_awaiting_confirmationA pull-request preview is waiting for someone to allow its plan to run — the manualsetting of a stack’s pull-request plan mode. Expect one per push to every open pull request on such a stack.deployment_awaiting_confirmationandpreview_awaiting_confirmationare the two events that ask you to do something rather than telling you what happened.Stack lifecycle. Unlike the deployment events these are caused by a person or a service account, and the payload says who.
Event Fires when stack_createdA stack was created. stack_deletedA stack was deleted. stack_renamedA stack was renamed. Renaming mints a new stack ID, so the payload carries the old one too — otherwise there is nothing connecting the two. stack_syncedA stack was pointed at a new commit. Re-syncing a stack that is already on that commit is silent. Repository sync.
Event Fires when scm_sync_failedAn inbound event from a repository ran out of retries and was given up on. This means GantryCD has quietly stopped reacting to that repository — pushes and pull requests there are no longer producing deployments. Rare, and worth acting on. This one is about a repository, not a stack, so an integration with a stack-label selector will not receive it: a selector says “only these stacks”, and this event concerns none in particular.
Dependency diagnostics.
Event Fires when explore_findings_changedOn a schedule you choose, reporting how many dependency cycles, orphan references and duplicate resources your organization currently has. You set the schedule as a cron-style expression in UTC —
minute hour * * day-of-week:Expression Runs 0 10 * * 1Mondays at 10:00 UTC 0 9 * * *Every day at 09:00 UTC 30 8 * * 1-5Weekdays at 08:30 UTC Day-of-week is 0-6 with 0 = Sunday, and accepts lists and ranges. The minute and hour must be single values, and day-of-month and month must both be
*— the report runs at most once a day, so there is no way to write an hourly one. Expect the report within about fifteen minutes of the time you set: a background job delivers it, and it does not run continuously.This one is a standing report, not an alert. These findings are a backlog to work through rather than incidents to react to, so it arrives on a rhythm you can build a habit around and tells you what is outstanding whether or not it changed since last time. Each report also shows the movement since the previous one, so you can see whether you are getting on top of it.
It reports counts, not individual findings: the sweep behind it recomputes everything from scratch each hour and keeps no history, so “which cycle is new” is a question the data cannot answer.
An organization with nothing to report is skipped, so a clean one stays quiet rather than sending an empty message every week. Nothing is recorded as sent in that case, so when something does appear it is reported at the next scheduled time rather than that occurrence being treated as already used up.
When an organization’s dependency graph is too large to analyse, cycles and orphans are not checked at all. The report says so rather than reporting them as zero.
There is no
stack_updated. “Updated” covers environment variables, labels, contexts and protected keys, each edited separately — it would be the noisiest event here and say the least. -
Only when something changed. An integration can set changes only, which drops a deployment notification when its plan ran and reported nothing to do. Deployments whose plan could not be measured are still delivered — the filter stays quiet only when it can prove there was nothing to say.
-
Stack-label routing. An integration can carry a stack-label selector (only notify for stacks whose labels match) and provider-specific routing keyed on labels (for Slack, a stack label can choose the channel). See Slack.
Checking it works
Every integration has a Test action, and it sends a message shaped like the event the
integration subscribes to — not always a deployment. If you subscribe to several, it uses the
first in the list above; add ?event_type= to the test request to pick another.
Worth doing once per provider after you set one up, because a message that a provider rejects (a channel the bot is not in, a payload it will not render) fails at send time, not when you save the configuration. A test that fails tells you immediately; a real event that fails becomes a dead-lettered delivery you have to go and look for.
These are the ones worth eyeballing, because each renders differently:
| Send a test for | Check that |
|---|---|
deployment_succeeded | The change counts appear, and read as counts rather than as +0 ~0 -0. |
deployment_awaiting_confirmation | The button says to confirm, and the link opens the deployment. |
run_command_executed | The command’s size appears and its text does not. |
stack_renamed | Both the old and new names appear. |
scm_sync_failed | The repository is named and the message leads with the consequence. |
explore_findings_changed | The counts appear and the link opens Insights. |
Always asynchronous, always retried
Notifications never run on the deployment’s critical path. When a deployment finishes, GantryCD writes a durable delivery row and a background worker sends it:
- A Postgres
LISTEN/NOTIFYwake delivers within moments; a one-minute scheduler sweep is the safety net if a wake is missed. - Each delivery is retried with backoff up to a fixed maximum, then dead-lettered (kept, not silently dropped) so a failing endpoint is visible rather than lost.
- A notification failure never blocks or fails a deployment.
- Every terminal deployment is covered — including failures surfaced by self-heal jobs — via a reconcile sweep, not just the fast path.
- Deliveries that have not gone out — in flight, retrying, or dead-lettered — are viewable at
GET /api/v1/orgs/{org_id}/notification-deliveries, and a dead-lettered one can be re-sent withPOST .../notification-deliveries/{delivery_id}/retry. Successful deliveries are not recorded — the row is deleted once the message goes out — so that list being empty means nothing is currently failing. It is not proof that everything was delivered: dead rows are pruned afterGANTRYCD_NOTIFICATION_DELIVERY_RETENTION, so an old failure eventually leaves the list.
For the internals — the provider interface, the outbox, retry/dead-letter, and the deployment hook — see Notifications internals.