WorkerDeck

Guides

Notifications

Reach a person who isn't watching — permission requests, finished turns, errors and closes, POSTed to a webhook.

A session that needs an approval is useless if nobody is looking at it. The session WebSocket is the live channel, but it only helps someone with a socket open — and a phone cannot hold one in the background. Session notifications are the way out: the server reaches you.

Four moments, chosen because they are the ones a person acts on:

TypeWhen
permission_requestedThe agent is blocked on an approval.
turn_completedA turn finished; the session is idle and waiting.
session_errorThe session failed.
session_closedThe session ended, whoever ended it.

This is a human-attention channel, not an event mirror. Everything else stays on the session WS — attach with afterSeq to catch up on what you missed.

Enabling

const worker = createWorkerServer({
  authenticate,
  notifications: {
    webhook: {
      url: 'https://my-app.test/hooks/session',
      headers: { authorization: '…' },
      events: ['permission_requested', 'session_error'], // default: all four
    },
    // In-process seam, unfiltered — fires whether or not a webhook is configured.
    onNotification: (n) => console.log(n.type, n.sessionId, n.preview),
  },
})

The config is server-wide, unlike the job queue’s per-job webhook — the whole point is hearing about sessions you did not create and are not attached to. Every registry session qualifies, job runs included, so a job carrying its own webhook is reported on both channels.

The payload

{
  "type": "permission_requested",
  "sessionId": "sess_…",
  "seq": 42,                  // attach with afterSeq: 41 to land on the event behind it
  "ts": 1767225600000,
  "preview": "Bash",          // one line fit for a notification body
  "session": { /* SessionInfo as the event left it — status, title, cwd, cost */ },
  "request": { /* the full PermissionRequest */ }
}

request rides along on permission_requested for one reason: with the request id in hand a consumer can answer over REST —

POST /v1/sessions/:sessionId/permissions/:requestId
{ "behavior": "allow" }

— which is what makes an Approve/Deny action on a lock-screen notification, or a button in a Slack message, work at all. See Permissions.

turn_completed carries result (isError, durationMs, numTurns, totalCostUsd); session_closed carries reason.

Delivery

Ordered per session, best-effort, retried with exponential backoff (attempts, default 3; retryDelayMs, default 500). A consumer that missed one can always attach to the session WS and read the truth — deliveries are a prompt, never the system of record.

Push notifications

The server holds no push credentials, by design: it speaks HTTP to a URL you control and knows nothing about APNs, FCM, Slack or email. Turning a notification into a phone push is a forwarder’s job — it needs credentials, and those belong to whatever you run in front of, or alongside, the gateway.