zudo-slack-notify

Type to search...

to open search from anywhere

POST /v1/notify

Request schema, limits, rendering, threading, and the success response.

Posts one notification to the Slack channel behind a target alias.

Request

POST /v1/notify HTTP/1.1
Host: zudo-slack-notify-app.zudolab.dev
Authorization: Bearer <your-relay-key>
Content-Type: application/json; charset=utf-8

{
  "target": "releases",
  "kind": "action_required",
  "title": "npm staging is ready for review",
  "message": "Checks passed. Complete the existing manual approval step.",
  "source": "my-project / local agent",
  "fields": [{ "label": "Version", "value": "1.2.3" }],
  "links": [{ "label": "Review", "url": "https://example.com/review" }]
}
  • Authorization must be Bearer followed by the relay key (1 to 256 printable ASCII characters).

  • Content-Type must be application/json, optionally with charset=utf-8.

  • Compressed request bodies are rejected: Content-Encoding must be absent or identity.

  • The body is UTF-8 JSON of at most 16 KiB.

Fields

Unknown top-level fields are rejected. All text is nonblank and free of control characters (tab, newline, and carriage return are allowed).

FieldTypeRequiredLimit and rules
targetstringyesAt most 64 characters; lowercase kebab-case alias configured in SLACK_TARGETS
messagestringyesAt most 2000 characters
kindstringnoinfo (default), success, warning, error, action_required
titlestringnoAt most 120 characters
sourcestringnoAt most 100 characters; a caller-supplied label, not a verified identity
fieldsarraynoAt most 6 objects of label (at most 60 characters) and value (at most 300)
linksarraynoAt most 3 objects of label (at most 60, no pipe or newline) and url
threadTsstringnoParent message timestamp matching ^\d{10,16}\.\d{6}$, for example 1750000000.000001

Link URLs must be absolute https:// URLs of at most 500 characters, with no whitespace, <, >, |, or backslash, and no embedded username or password. Field and link objects reject unknown keys.

Rendering

The Worker builds the Slack blocks; callers never send Block Kit.

ElementSlack shapeLimit after escaping
Header: Kind: Title (or just Kind)header block150 characters
Messagesection block3000 characters
Each field, as label newline valueone section block with a fields list2000 characters each
Linksone section block, <url|label> per line3000 characters
Sourcecontext block, Source: …2000 characters
Fallback texttop-level text for notifications and accessibility40000 characters

The kind labels are Info, Success, Warning, Error, and Action required. The message has at most 50 blocks.

Text is escaped for Slack (&, <, >) before the limits are checked, so text near a limit that contains many of those characters can exceed it after escaping and fails with rendered_message_too_long. Messages are sent with parse: none, link_names: false, unfurl_links: false, unfurl_media: false, and reply_broadcast: false, so a caller cannot trigger mentions, @channel, unfurls, or channel broadcasts. Only validated links use Slack link markup.

Threading with threadTs

Every success response returns ts, the Slack timestamp of the posted message. To reply in that message's thread, send a later notification to the same target with threadTs set to that ts:

{ "target": "releases", "message": "Approved and published.", "threadTs": "1750000000.000001" }

Replies are never broadcast to the channel. If the parent cannot take replies Slack answers with a rejection such as cannot_reply_to_message, which surfaces as slack_rejected. The API does not remember receipts; the caller keeps the ts.

Success response

200 OK:

{
  "ok": true,
  "delivery": "sent",
  "requestId": "0d6c1c53-6d6c-4f7f-9a3e-7f9d5a5b7d11",
  "target": "releases",
  "channel": "C0123456789",
  "ts": "1750000000.000001"
}

delivery: "sent" is returned only after the Worker has verified Slack's receipt: ok: true, the same channel it posted to, and a well-formed ts. Anything less is reported as unknown. See Delivery semantics.

Order of checks

A request is refused at the first check it fails, in this order: route and method, Worker configuration (503 server_misconfigured), authentication, content type and encoding, body size and JSON, field validation, target lookup, rendering, then the single Slack attempt. Because configuration comes first, an unwired Worker never authenticates anyone or contacts Slack. See the error catalog for every code.

Revision History

CreatedUpdated