Notifications
A destination is a Slack channel, a webhook or an email address that hears your organization's events. Add one under Settings → Notifications, send it a test, and the delivery log shows what the receiver answered. Nothing is sent until a destination exists.
The bell
Every member sees the organization's notifications in the console: the bell before your avatar shows how many arrived since you last opened it, and the list links each one to the app or cluster it is about. Nothing has to be set up for that. Destinations are for hearing about it somewhere else.
Events
| Event | When | On by default |
|---|---|---|
deploy.succeeded | A build finished and the new version is serving | yes |
deploy.failed | A build or a rollout failed; the previous version is still serving | yes |
cluster.disconnected | A cluster's agent has been unreachable for two minutes | yes |
bandwidth.spike | Gateway traffic is far above the organization's usual daily level. Nothing is suspended | yes |
app.degraded | An app has been unhealthy for five minutes | no |
service.degraded | A managed service has been unhealthy for five minutes | no |
A destination that names no events hears the four defaults. The health events are opt-in because they are judgements about a duration rather than facts, and a judgement that fires too early teaches people to mute the channel.
One message, then one more
A rollout makes an app unhealthy on purpose for a few seconds; a restart backoff and a node
drain do too. So app.degraded, service.degraded and cluster.disconnected fire only once the
condition has held for its delay, and they fire once. Still degraded an hour later sends
nothing further. When it recovers you get exactly one closing message, app.recovered,
service.recovered or cluster.reconnected, naming how long it lasted, and only if the
degradation was announced. A destination subscribed to the alert hears the recovery; there is
nothing to subscribe to separately.
A deploy that fails and leaves an app degraded produces deploy.failed at once and
app.degraded five minutes later. That is two messages about one thing on purpose: the first
says the deploy did not work, the second says the app is still not working.
Destinations
- Slack takes an incoming-webhook URL and renders a Block Kit message. The URL is the credential; it is encrypted at rest and the list shows only its host.
- Webhook takes any HTTPS URL and receives the JSON below, signed.
- Email takes an address and receives the same message as the console's alert mails.
Each destination can be narrowed to particular projects. An event with no project, such as a cluster disconnect, reaches only destinations that are not narrowed.
Sends are capped at 60 per destination per hour; a delivery dropped by the cap is recorded as
suppressed in the log rather than silently lost.
Webhook payload
{
"version": 1,
"id": "68c4…:deploy.failed",
"type": "deploy.failed",
"occurredAt": "2026-09-13T12:04:11Z",
"organization": "acme",
"project": "demo",
"cluster": "prod",
"resource": { "kind": "app", "name": "checkout-api" },
"title": "checkout-api failed to deploy",
"summary": "Build 4f2a1c9 failed: exit status 1.",
"url": "https://app.anyport.dev/orgs/…/projects/demo/apps/checkout-api",
"details": { "commit": "4f2a1c9", "author": "ada", "reason": "exit status 1" }
}
Every request carries four headers:
| Header | Value |
|---|---|
X-Anyport-Event | The event type |
X-Anyport-Delivery | The delivery id, unique per attempt; the body's id is the event's, shared by retries |
X-Anyport-Timestamp | Unix seconds when the request was signed |
X-Anyport-Signature-256 | sha256= + HMAC-SHA256 over timestamp + "." + body, keyed with the signing secret |
The signing secret is shown once when the webhook is added and can be rotated from the same
panel. Verify the signature and reject timestamps older than a few minutes, and deduplicate on
the body's id: a receiver that was down gets the same event again on retry, with the same id.
Retries follow a fixed schedule of 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, about
nine hours in all. A 4xx other than 429 is not retried: the destination is wrong, and the row says so.
The log
Settings → Notifications → Deliveries lists every attempt for 30 days with the receiver's status and, for a refusal, its response. From a terminal:
anyport notify destination ls
anyport notify log --destination team-slack
An empty log means nothing has been sent, never that something was lost: the row is written before the first attempt.
Next steps
- Deploy from GitHub — What produces
deploy.succeededanddeploy.failed. - Connecting clusters — What
cluster.disconnectedis measuring. - CLI —
anyport notifyand the rest of the terminal surface.