Notifications API
z4j’s notification surface has two halves. Project channels are operator-owned destinations a project sends to (Slack webhook for #ops, PagerDuty key, SMTP server for invitation emails). User channels and subscriptions are personal: each user picks the triggers they want notifications for and which of their personal channels deliver them, optionally bridged to project channels.
The catalog below maps every shipped route. Channel config is per-type JSON; see SMTP presets for email-specific shape.
Triggers
Section titled “Triggers”Every subscription and default binds to one of these trigger keys:
task.failed schedule.fire.failedtask.succeeded schedule.fire.succeededtask.retried schedule.task_failedtask.slow schedule.circuit_breaker.trippedagent.offlineagent.onlineThe list is enforced server-side; submitting a different value fails validation.
Channel types
Section titled “Channel types”webhook email slack telegram pagerduty discordEach config shape differs. Examples:
| Type | Config example |
|---|---|
webhook | {"url": "https://...", "headers": {"X-Custom": "v"}, "hmac_secret": "optional"} |
email | {"smtp_host": "...", "smtp_port": 587, "smtp_user": "...", "smtp_pass": "...", "smtp_tls": true, "from_addr": "...", "to_addrs": [...]} |
slack | {"webhook_url": "https://hooks.slack.com/services/..."} |
telegram | {"bot_token": "...", "chat_id": "..."} |
pagerduty | {"integration_key": "...", "severity": "error"} |
discord | {"webhook_url": "https://discord.com/api/webhooks/..."} |
config is JSON and capped at 16 KiB.
Project channels
Section titled “Project channels”Base path: /api/v1/projects/{slug}/notifications/channels. All routes require admin on the project.
List channels
Section titled “List channels”GET /api/v1/projects/{slug}/notifications/channelsReturns ChannelPublic[]:
[ { "id": "...", "project_id": "...", "name": "ops slack", "type": "slack", "config": {"webhook_url": "<redacted>"}, "is_active": true, "created_at": "...", "updated_at": "..." }]Create
Section titled “Create”POST /api/v1/projects/{slug}/notifications/channels{ "name": "ops slack", "type": "slack", "config": {"webhook_url": "https://hooks.slack.com/services/..."}, "is_active": true}CSRF-protected. The brain validates the config shape (SSRF guards on webhook URLs, port allow-list on SMTP, etc.) before persisting.
Import a personal channel into the project
Section titled “Import a personal channel into the project”POST /api/v1/projects/{slug}/notifications/channels/import_from_user{ "user_channel_id": "...", "name": "Copy of my Slack" // optional; defaults to "Copy of {original}"}Copies an admin’s personal channel into the project so the secret never has to be re-pasted. The source channel must be owned by the calling admin; the brain refuses to import another user’s channel.
Update / delete
Section titled “Update / delete”PATCH /api/v1/projects/{slug}/notifications/channels/{channel_id}DELETE /api/v1/projects/{slug}/notifications/channels/{channel_id}CSRF-protected. Patch body is a subset of the create body.
Test a saved channel
Section titled “Test a saved channel”POST /api/v1/projects/{slug}/notifications/channels/{channel_id}/testDispatches a single test payload through the saved channel and returns:
{ "success": true, "status_code": 200, "error": null, "response_body": null}Test unsaved config
Section titled “Test unsaved config”POST /api/v1/projects/{slug}/notifications/channels/testSame response shape; takes the full {type, config} body so admins can verify credentials in the create dialog before persisting. Nothing is written to the DB.
Project default subscriptions
Section titled “Project default subscriptions”Project admins can set defaults so newly-joining members start with sensible subscriptions instead of empty inboxes.
Base path: /api/v1/projects/{slug}/notifications/defaults. All routes require admin.
GET /api/v1/projects/{slug}/notifications/defaultsPOST /api/v1/projects/{slug}/notifications/defaultsPATCH /api/v1/projects/{slug}/notifications/defaults/{default_id}DELETE /api/v1/projects/{slug}/notifications/defaults/{default_id}Default body:
{ "trigger": "task.failed", "filters": {"priority": ["critical", "high"]}, "in_app": true, "project_channel_ids": ["..."]}filters.priority is constrained to critical / high / normal / low; task_name is a glob (fnmatch) capped at 500 chars.
Project delivery log
Section titled “Project delivery log”GET /api/v1/projects/{slug}/notifications/deliveriesDELETE /api/v1/projects/{slug}/notifications/deliveriesRole: admin. Returns the project’s recent delivery attempts (per-trigger / per-channel / per-status). The DELETE form purges the log (useful after a noisy incident); it does not affect the audit log.
User channels
Section titled “User channels”Personal channels owned by the calling user. Same shape as project channels; admin role not required.
Base path: /api/v1/user/channels.
GET /api/v1/user/channelsPOST /api/v1/user/channelsPATCH /api/v1/user/channels/{channel_id}DELETE /api/v1/user/channels/{channel_id}POST /api/v1/user/channels/testPOST /api/v1/user/channels/{channel_id}/testPOST /api/v1/user/channels/import_from_project mirrors the project-side import: copy a project channel into your personal channels (e.g. so you can subscribe to it for triggers the project does not default to).
User subscriptions
Section titled “User subscriptions”A subscription binds (trigger, filters) -> (in-app yes/no, user channel ids, project channel ids) for one user.
Base path: /api/v1/user/subscriptions.
GET /api/v1/user/subscriptionsPOST /api/v1/user/subscriptionsPATCH /api/v1/user/subscriptions/{sub_id}DELETE /api/v1/user/subscriptions/{sub_id}Create body:
{ "trigger": "task.failed", "filters": {"priority": ["critical"], "task_name": "billing.*"}, "in_app": true, "user_channel_ids": ["..."], "project_channel_ids": ["..."], "cooldown_seconds": 60}cooldown_seconds (default zero) suppresses duplicate notifications for the same (subscription, deduplication key) within the window.
User delivery log
Section titled “User delivery log”GET /api/v1/user/deliveriesRecent deliveries to the calling user’s channels.
In-app inbox
Section titled “In-app inbox”In-app notifications surface in the dashboard’s bell icon. Each subscription with in_app=true produces an inbox row when its trigger fires.
GET /api/v1/user/notificationsGET /api/v1/user/notifications/unread-countPOST /api/v1/user/notifications/{notification_id}/readPOST /api/v1/user/notifications/read-allunread-count is what the bell badge polls; mark-as-read flips a single row, read-all flips every row.
Webhook signing
Section titled “Webhook signing”Webhook channels with hmac_secret set sign every outbound POST with an X-Z4J-Signature header (HMAC-SHA256 over the raw body). Receivers can verify integrity without needing TLS.
HTTP-only webhooks
Section titled “HTTP-only webhooks”Outbound webhook URLs must be https:// by default. To allow plaintext for an intranet receiver, set Z4J_NOTIFICATIONS_WEBHOOK_ALLOW_HTTP=true. The check fires at both config-validation and dispatch time so an existing http:// URL stops working immediately if the flag is unset later. See production hardening.