API Reference
One endpoint, one POST request. Send JSON to your channel's webhook URL and we handle notifications, formatting, and delivery.
Send Alert
Trigger a new alert in a specific channel. This is the primary endpoint for sending notifications from your application.
curl -X POST https://zenhook.dev/api/webhook/abc123def456 \
-H "Content-Type: application/json" \
-d '{
"title": "New signup",
"level": "success",
"emoji": "🎉",
"source": "My App",
"actor": {
"id": "42",
"name": "Jane Doe",
"email": "[email protected]",
"plan": "Pro"
},
"linkUrl": "https://myapp.com/admin/users/42",
"linkText": "View Profile"
}'Path Parameters
Request Body
info, success, warning, error. Defaults to info."🚀"), a name ("rocket"), or a shortcode (":rocket:"). Names and shortcodes are translated on ingest. Unknown names are dropped rather than rendered as literal text. Known names: rocket · siren · warning · check · cross · fire · bug · zap · bell · tada · sparkles · envelope · chat · phone · megaphone · wave · eyes · heart · thumbsup · office · door · house · factory · lock · unlock · key · link · package · gear · wrench · shield · money · chart · clock · hourglass · flag · star · target · pin · tag · computer · server · database · cloud · bulb · gem · anchor · pick · bank.{ name, email, id, plan, company, location, avatarUrl, url }, any subset of it, or just a string. Also accepted as user. From an email alone Zenhook fills in the display name, the initials, the company behind a work domain, and the Gravatar if the person has one.label and value.name is accepted in place of label, and a value may be a number or a boolean.linkUrl is set.Errors are RFC 9457problem documents, served as application/problem+json. Branch on type, show title. The old error field is still there, so integrations written before the change keep working.
{
"type": "https://zenhook.dev/problems/unknown-token",
"title": "Invalid webhook token",
"status": 404,
"error": "Invalid webhook token"
}Send an Idempotency-Key header and a retry of the same request returns the first answer instead of creating a second alert, for 24 hours. The replay carries Idempotency-Replayed: true. A retry sent while the first request is still running gets a 409 rather than a duplicate. Worth setting on anything that retries on timeout, a deploy script above all.
Responses
Alert received and queued for delivery. destinationnames the workspace and channel the token resolved to, so a misrouted integration shows up in the response instead of surfacing days later in the wrong channel.
{
"success": true,
"alertId": "clx1234567890abcdef",
"destination": {
"workspace": { "id": "ws_...", "name": "Acme Corp" },
"channel": { "id": "ch_...", "name": "deploys", "slug": "deploys" }
}
}Use https://zenhook.dev/api/ for integrations. The www API alias also accepts requests without redirecting them. If a webhook or monitor heartbeat exceeds its request rate, the 429 response includes Retry-After: wait that many seconds before retrying. A monthly event allowance is separate; waiting a minute does not renew it.
Workflow notifications
A deploy or release is one process, not five alerts. Add aworkflow block and every POST that carries the samerunId updates a single alert in place: the card shows each step live, and the feed never fills with intermediate noise.
# every POST of a run carries the same runId
curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \
-H "Content-Type: application/json" \
-d '{
"title": "myapp deploy",
"workflow": {
"runId": "myapp-20260722-1015",
"state": "running",
"steps": [
{ "name": "test", "state": "done", "seconds": 46, "info": "716 tests" },
{ "name": "build", "state": "running" },
{ "name": "deploy", "state": "pending" }
]
}
}'States: the run is running, passed or failed; each step is pending, running, done, failed or skipped. Send the FULL steps array every time; the server replaces, it never merges.
Notification policy: the first POST notifies (started), running updates are silent, and the terminal state notifies once and resurfaces the alert as unread. The alert level is derived from the state, so a failed run can never claim success.
Metering: one process is one billable event, counted at the first POST. Updates are free.
On failure: pass workflow.error with the log tail; it renders on the card so the failure can be diagnosed from a phone.
Structured Data
Use the fields array to include rich metadata displayed in the dashboard and email alerts.
{
"title": "Build Failed",
"level": "error",
"fields": [
{ "label": "Branch", "value": "main" },
{ "label": "Commit", "value": "abc123f" },
{ "label": "Author", "value": "John Smith" },
{ "label": "Error", "value": "npm install timed out after 300s" }
],
"linkUrl": "https://github.com/org/repo/actions/runs/123",
"linkText": "View Logs"
}Widgets in the feed
There is nothing to configure. Zenhook reads each kind of event from its history and draws the widget that fits it. A kind of event is a stream: the same channel and the same title, numbers ignored, so Order 1284 paid andOrder 1285 paid are one stream. Keep the title stable and put what changes inmessage, fields oractor.
- Counter, the default: any stream that repeats. Today's count, the change against yesterday at the same hour, bars per hour. Nothing to send.
- Pipeline: a title with deploy, build, release, pipeline or CI, or a workflow block. The last runs and their pass rate. Send
level: "error"when a run fails. - Error:
level: "error". How many times today, since when, the message and your link. - Money: a field labeled Amount, Price, Total, Revenue or Paid, such as
$49.00or49 EUR. The day's total. - Person: an
actor(oruser). Who, and how many today and this week. - Streak: an event that happens about once a day, such as a sync or a backup. Days in a row.
- A one-off event stays a card with everything visible. Open a widget to read its events.
curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \
-H "Content-Type: application/json" \
-d '{
"title": "Payment received",
"level": "success",
"actor": "[email protected]",
"fields": [{ "label": "Amount", "value": "$49.00" }]
}'Reading alerts back out
The webhook endpoint above is public and write-only, anyone with the channel token can post into it. To list alerts, mark them read, or build an integration that consumes them, use the authenticated Read API with a Bearer token.