Skip to content

Get notified ​

The agent can tell you when something happened that you would act on: a deployment succeeded, failed or was rolled back, an application stopped serving or came back, a job failed. This page shows how to point the agent at a webhook, what Slack and Discord receive, what any other endpoint receives, when each of the six events fires and what does not notify, how to verify the signature, and how delivery behaves when the endpoint is slow or down.

Before you begin ​

  • Notifications are configured on the agent, not per application: one webhook receives the events of every application on the server.
  • The URL must be https://, unless it points at localhost or a private address, where plain http:// is accepted. A Slack or Discord webhook URL is a credential: whoever has it can post to the channel.
  • Setting or changing the variable restarts the agent container. Running applications are not affected by an agent restart.

Set the webhook ​

Add the URL to the agent's environment file on the server and apply it:

bash
# /opt/shipwick/.env
SHIPWICK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/XXXX
SHIPWICK_WEBHOOK_SECRET=a-long-random-string   # optional, see Verify the signature
bash
cd /opt/shipwick && docker compose up -d

Compose recreates the agent with the new environment. The agent's log records that notifications go to a webhook and names its host, hooks.slack.com, and nothing more of the URL. A URL that does not qualify stops the agent at startup with a message that names the rule and never repeats the URL: SHIPWICK_WEBHOOK_URL: must use https; http is allowed only for localhost and private addresses.

Then check from anywhere:

bash
shipwick server status
text
Notifications   webhook configured

Without a webhook the line reads none (set SHIPWICK_WEBHOOK_URL on the agent). The same fact is notifications: {"webhook": true} in GET /server, and a Notifications row on the dashboard's Servers page.

Slack and Discord ​

Two destinations get a plain message, because that is all they render:

DestinationRecognised byBody
SlackThe host hooks.slack.com{"text": "<message>"}
DiscordThe host discord.com or discordapp.com with a path under /api/webhooks/{"content": "<message>"}

The message is one sentence that says what happened and, where there is one, what to do next:

text
my-api deployment of 1.4.3 failed: replica 1 exited with code 1 shortly after start. my-api is still running 1.4.2; the failed deployment did not affect it

Any other endpoint ​

Every other URL receives the same sentence with the facts beside it, as JSON:

json
{
  "event": "deployment.succeeded",
  "application": "my-api",
  "deployment_id": 42,
  "version": "1.4.2",
  "message": "my-api is running 1.4.2, replacing 1.4.1",
  "at": "2026-03-01T10:00:00Z",
  "server": "vps-1"
}
Field
eventOne of the six kinds below
applicationThe application's name
deployment_idThe deployment the event is about; null for job.failed, which is not about one
versionThe version concerned: the one deployed, failed, restored, running or whose job failed
messageThe sentence a chat destination would get
atWhen it happened, UTC, to the second
serverThe hostname of the server the agent runs on

The request is a POST with Content-Type: application/json and User-Agent: shipwick-agent/<version>. Any 2xx counts as delivered.

What is sent, and when ​

EventWhenMessage
deployment.succeededA deployment or redeploy became ACTIVEmy-api is running 1.4.2, replacing 1.4.1, or my-api was redeployed and is running 1.4.2
deployment.failedA deployment ended FAILED: the running version was never touchedmy-api deployment of 1.4.3 failed: <why>. my-api is still running 1.4.2; the failed deployment did not affect it. For an application with nothing running: Nothing of my-api is running. Fix the cause and deploy again; the events say more: shipwick status my-api
deployment.rolled_backA deployment failed part-way and the previous version was restored, or shipwick rollback succeededmy-api deployment of 1.4.3 failed: <why>. Rolled back: my-api is running 1.4.2 again, or my-api rolled back to 1.4.1 from 1.4.2
application.downNot one replica of a running application is ready: none running, or all failing the health checkmy-api is down: none of its 2 replicas is running. Shipwick restarts it as restart.policy allows; see why with: shipwick logs my-api
application.recoveredAfter application.down, every desired replica is ready again and none has a restart still held against it, which takes a minute of runningmy-api is healthy again: 2/2 replicas running 1.4.2
job.failedA scheduled job, a one-off command or a pre-deploy hook failed or timed outmy-api: Job nightly-report failed (exit 1). Its output: shipwick jobs logs my-api nightly-report

Nothing else is ever sent. A single replica restarting, one failed health check, a crash loop that has not taken the last replica down, shipwick stop and start, a successful job: all of it is in the application's event feed and in shipwick status, not in your chat. An application stopped on request is not down; it is stopped, and the supervisor does not watch it until it is started again.

The recovery rule is strict on purpose. A replica that crash-loops runs for a moment between crashes; reporting that moment as a recovery would produce a recovery and a new outage at every restart. The recovery is reported once the replicas have run long enough for their restarts to be forgiven, as described in Health checks and supervision.

Verify the signature ​

With SHIPWICK_WEBHOOK_SECRET set, every request carries

http
X-Shipwick-Signature: sha256=<hex>

where <hex> is the HMAC-SHA256 of the request body, keyed with the secret, in lowercase hexadecimal. To verify: read the raw body exactly as received, compute the HMAC-SHA256 of those bytes with the secret you configured, hex-encode it, prefix sha256=, and compare with the header using a constant-time comparison. A request without the header, or with one that does not match, was not sent by your agent. The header is only present when the secret is set; Slack and Discord ignore it.

Delivery ​

  • A notification never holds up a deployment. Events are queued and sent by one sender, in order; the engine and the supervisor hand an event over and carry on. A webhook that is slow or down costs nothing but the notification.
  • Each attempt has 10 seconds. A connection failure, a 5xx or a 429 is retried after 1, 5 and 25 seconds, then given up. Any other rejection, a 4xx, is given up at once: a request the endpoint refused once it will refuse again.
  • The queue holds 256 events. If it is full, the newest event is dropped and the drop is logged. When the agent shuts down, it spends up to 5 seconds delivering what is queued.
  • Only the host is logged. A delivery that fails or is dropped appears in the agent's log with the event kind, the application and the webhook's hostname. The URL, its path and its token, and the secret are never written anywhere.

What's next ​