Alerts

An alert tells you that something in a workspace needs attention, without anyone watching the console. Seamward sends it to a destination you add: a webhook your service receives, or an email to members of the workspace.

An alert carries only what the console already shows: the kind of event, the workspace, the connection, one summary sentence, and a link. It never contains payloads, headers, credentials, or record identifiers.

Events

EventSent when
incident.openedAn incident is opened for a connection.
incident.resolvedAn incident recovers and stays healthy through verification, or your team resolves it.
connection.quietA Live connection has sent nothing for longer than its own history says is normal.
repair.awaiting_approvalA proposed repair passed validation and is waiting for your approval.

Each event is sent once per destination. An incident that stays open, or a connection that stays quiet, does not alert again.

A destination chooses which events it receives and for which modes. New destinations default to Live only, because Sandbox connections are often quiet or failing on purpose.

Add a destination

Owners and admins manage destinations in Workspace settings, then Alerts. Viewers can see the list and recent deliveries. A workspace can have up to ten destinations.

  1. Select Add destination.
  2. Choose Webhook or Email and give it a name.
  3. Enter the URL, or choose the recipients.
  4. Choose the events and modes, then select Add destination.
  5. Select Send test on the new destination, then open Recent deliveries to confirm it arrived.

On a workspace with no destinations, Email owners and admins creates an email destination for every event in Live in one step.

Webhook destinations

The URL must be a public HTTPS address. Addresses on local, private, or internal networks are refused, both when you add the destination and again each time an alert is sent.

When you add a webhook, Seamward shows its signing secret once. Store it where your receiver can read it. It cannot be shown again; to replace it, delete the destination and add a new one. Afterwards the console shows only the URL's host.

Email destinations

Email goes to members of the workspace with a verified address, never to an outside address:

  • Workspace owners and admins follows the workspace. Whoever holds either role when an alert is sent receives it.
  • Chosen members sends to up to ten members you pick.

Each recipient gets a separate message. A member who leaves the workspace stops receiving alerts immediately.

Receive a webhook

Seamward sends a POST with a JSON body and three headers:

HeaderValue
x-seamward-signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
x-seamward-eventThe event type, for example incident.opened.
x-seamward-alert-idThe alert's id. It is the same on every attempt for that event.

Code example

{  "schemaVersion": "seamward.alert/1",  "id": "alr_3f9c2a7d41e85b06c9d2e7f4a1b83c50",  "type": "incident.opened",  "occurredAt": "2026-10-05T10:45:00.000Z",  "workspace": { "slug": "acme", "name": "Acme" },  "mode": "live",  "connection": { "id": "int_8Hq2mPz4", "name": "Order webhooks" },  "summary": "3 order outcomes missing after successful order.create events",  "url": "https://seamward.com/app/acme/incidents/inc_5tR9wLk2?environment=live"}
FieldTypeNotes
schemaVersionstringAlways seamward.alert/1 for this format.
idstringStable for the event. Use it to ignore an alert you have already handled.
typestringOne of the four events above, or test for a test alert.
occurredAtstringWhen the event happened, as an ISO 8601 UTC timestamp.
workspaceobjectThe workspace's slug and name.
modestringlive or sandbox.
connectionobjectThe connection's id and name. Absent on a test alert.
summarystringThe sentence the console shows for the incident, silence, or repair. Up to 500 characters.
urlstringWhere to review it in the console.

Verify the signature

Verify every request before you act on it. The signature is an HMAC-SHA256 of the timestamp, a full stop, and the exact request body, keyed with the destination's signing secret.

Three things matter: use the raw body bytes as received (parsing and re-serializing JSON changes them), compare in constant time, and reject old timestamps so a captured request cannot be replayed.

Code example

import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
export function isSeamwardAlert(  rawBody: string,  signatureHeader: string,  signingSecret: string,): boolean {  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(signatureHeader);  if (!match) return false;  const timestamp = Number(match[1]);  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
  const expected = createHmac("sha256", signingSecret)    .update(`${timestamp}.${rawBody}`, "utf8")    .digest();  const given = Buffer.from(match[2]!, "hex");  return given.length === expected.length && timingSafeEqual(given, expected);}

A handler that uses it, with Node's built-in server so the raw body is never touched:

Code example

import { createServer } from "node:http";
createServer(async (request, response) => {  const chunks: Buffer[] = [];  for await (const chunk of request) chunks.push(chunk as Buffer);  const rawBody = Buffer.concat(chunks).toString("utf8");
  const signature = request.headers["x-seamward-signature"];  if (    typeof signature !== "string" ||    !isSeamwardAlert(      rawBody,      signature,      process.env.SEAMWARD_ALERT_SIGNING_SECRET!,    )  ) {    response.writeHead(401).end();    return;  }
  const alert = JSON.parse(rawBody) as {    id: string;    type: string;    url: string;  };  // Hand the alert to your own queue or pager here, keyed on alert.id.  response.writeHead(204).end();}).listen(8080);

If your framework parses JSON for you, configure it to keep the raw body for this route.

Respond, and what is retried

Answer with any 2xx status within 5 seconds. Do slow work after you have answered.

Your responseWhat Seamward does
2xxRecords the alert as delivered.
408, 429, any 5xx, timeout, no answerTries again, up to six attempts over about four minutes.
Any other status, including a redirectRecords the alert as failed. It is not retried.

A retry carries the same id, so a receiver that has already handled it can answer 2xx and do nothing. Redirects are never followed. A destination that keeps failing is not paused automatically.

Test, pause, and review deliveries

  • Send test queues a test alert through the same path as a real one, so a test that arrives proves the URL, the signature, and delivery.
  • Recent deliveries lists the last 20 alerts for a destination with their outcome, the number of attempts, and the reason for a failure. Delivery records are kept for 30 days.
  • The Sending switch pauses a destination without deleting it. Nothing is sent to a paused destination, and events that happen while it is paused are not sent later.

Troubleshooting

Recent deliveries showsWhat to check
The destination answered with a statusYour receiver refused the request. A 401 usually means the signing secret or the raw-body handling is wrong.
The destination did not answer within 5 secondsAnswer first, then do the work. Check that the URL is reachable from the public internet.
The address resolved to a private networkThe URL's name points at a private or internal address. Use a publicly reachable address.
The destination's name could not be resolvedThe URL's host name does not exist or its DNS is failing.
No verified workspace member was availableAn email destination has no recipient left. Check roles and email verification under Members.
At least one recipient's address was rejectedThe mail service refused an address. The other recipients still received the alert.

What is not available

Webhook and email are the two destination types today. Destinations for chat, paging, and ticketing tools are planned and are not available. A webhook can reach those tools through your own receiver.