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.

POSThttps://zenhook.dev/api/webhook/{token}
Example Request
curl -X POST https://zenhook.dev/api/webhook/abc123def456 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "New User Signup",
    "level": "success",
    "emoji": "馃帀",
    "source": "My App",
    "message": "[email protected] just joined your workspace.",
    "linkUrl": "https://myapp.com/admin/users/42",
    "linkText": "View Profile"
  }'

Path Parameters

tokenstringRequired
Your unique channel webhook token. Find it in channel settings.

Request Body

titlestringRequired
A brief summary of the alert. Used as the headline in notifications. Max 200 characters.
levelenum
Severity of the alert: info, success, warning, error. Defaults to info.
messagestring
Detailed body text. Max 5,000 characters.
emojistring
Custom emoji displayed next to the title. Accepts a unicode character ("馃殌"), 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.
fieldsarray
Key-value objects for structured data. Each object has label and value.
linkUrlstring
URL for a clickable call-to-action link displayed below the title.
linkTextstring
Label for the link button. Defaults to "View" if linkUrl is set.
attachmentsarray
Up to 8 media items shown inside the alert. Each object takes aurl plus an optionaltype(image,video, oraudio) andname caption. Zenhook fetches each URL and re-hosts the file on its own CDN — your media loads fast and your source URLs stay private. Supported: PNG, JPEG, GIF, WebP, AVIF, MP4, WebM, MP3, WAV, M4A, Ogg (SVG is rejected). Max 10 MB per image, 25 MB audio, 50 MB video.
Direct file uploadmultipart
No public URL for your file? Send the bytes directly. POSTmultipart/form-data with apayload part carrying the same JSON fields, plus one file part per attachment. The simplest client is a title and a file:
curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \
  -F 'payload={"title":"Build failed","level":"error"}' \
  -F 'file=@./screenshot.png'
Same limits as URL attachments: up to 8 files, 10 MB per image, 25 MB audio, 50 MB video.
sourcestring
Author name shown in the alert header (e.g. "GitHub", "Stripe"). Defaults to "Webhook".
metadataobject
Arbitrary JSON object shown as an expandable "Raw Payload" section with copy-to-clipboard. Max 10 KB.

Responses

201 Created

Alert received and queued for delivery.

json
{
  "success": true,
  "alertId": "clx1234567890abcdef",
  "queuedAt": "2024-03-22T12:00:00Z"
}

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.

bash
# 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.

Example with fields
{
  "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" }
  ],
  "attachments": [
    { "url": "https://ci.example.com/runs/123/screenshot.png", "type": "image" }
  ],
  "linkUrl": "https://github.com/org/repo/actions/runs/123",
  "linkText": "View Logs"
}

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.