### Notification preference cascade example Source: https://zenhook.dev/docs/permissions A practical example showing how channel-specific overrides can bypass workspace-level muting. ```text Alice's defaults: pushAlerts = true Workspace override: pushAlerts = false // muted this workspace Channel #payments: pushAlerts = true // but this channel still pages Result: Alice gets push for #payments only. ``` -------------------------------- ### Token Format Example Source: https://zenhook.dev/docs/api/read The expected format for Zenhook API tokens. ```text zhk_<48 hex chars> # example: zhk_a1b2c3d4e5f6... ``` -------------------------------- ### List Channels Response Source: https://zenhook.dev/docs/api/read Example JSON response structure for the channels endpoint. ```json [ { "id": "clx_chan_1", "name": "production-errors", "slug": "production-errors", "icon": "🔴", "isPrivate": false, "webhookToken": "abc123...", "_count": { "alerts": 3 } } ] ``` -------------------------------- ### Alert list response structure Source: https://zenhook.dev/docs/api/read Example JSON response containing a list of alerts and pagination metadata. ```json { "alerts": [ { "id": "clx_alert_1", "channelId": "clx_chan_1", "title": "Build Failed", "level": "ERROR", "emoji": "🔴", "message": "npm install timed out after 300s", "fields": [{ "label": "Branch", "value": "main" }], "linkUrl": "https://github.com/org/repo/actions/runs/123", "isRead": false, "createdAt": "2026-04-27T14:32:11.000Z" } ], "nextCursor": "clx_alert_42", "hasMore": true } ``` -------------------------------- ### GET /api/channels Source: https://zenhook.dev/docs/api/read Returns a list of channels the authenticated user can access, including public channels and private channels where the user is a member. ```APIDOC ## GET /api/channels ### Description Returns the channels the token's user can access. Each channel includes an `_count.alerts` field containing the user's personal unread count. ### Method GET ### Endpoint https://zenhook.dev/api/channels ### Response #### Success Response (200) - **id** (string) - Channel identifier - **name** (string) - Channel name - **slug** (string) - Channel slug - **icon** (string) - Channel icon - **isPrivate** (boolean) - Whether the channel is private - **webhookToken** (string) - Webhook token for the channel - **_count** (object) - Contains the `alerts` count ``` -------------------------------- ### GET /api/channels/feedback/alerts Source: https://zenhook.dev/docs/api/read Retrieves feedback alerts from connected channels, such as Discord, with optional filtering. ```APIDOC ## GET /api/channels/feedback/alerts ### Description Retrieves alert objects from connected channels. For Discord channels, this includes metadata about the message author, reply context, and attachments. ### Method GET ### Endpoint https://zenhook.dev/api/channels/feedback/alerts ### Parameters #### Query Parameters - **limit** (integer) - Optional - The maximum number of alerts to return ### Response #### Success Response (200) - **id** (string) - Alert identifier - **message** (string|null) - The text content of the message - **createdAt** (string) - ISO timestamp of message creation - **author** (object) - Author details including `id`, `name`, `bot`, and `role` - **replyTo** (object|null) - Parent message context if the alert is a reply - **attachments** (array) - List of file attachments ``` -------------------------------- ### GET /api/channels/{id or slug}/alerts Source: https://zenhook.dev/docs/api/read Retrieves a paginated list of alerts for a specific channel, ordered by newest first. ```APIDOC ## GET /api/channels/{id or slug}/alerts ### Description Retrieves a paginated list of alerts for a specific channel. Each alert includes an `isRead` status specific to the calling user. ### Method GET ### Endpoint https://zenhook.dev/api/channels/{id or slug}/alerts ### Parameters #### Path Parameters - **id or slug** (string) - Required - The ID or slug of the channel. #### Query Parameters - **cursor** (string) - Optional - Alert ID returned as `nextCursor` in the previous page. - **limit** (number) - Optional - 1-100. Defaults to 50. - **level** (enum) - Optional - Filter by severity: `info`, `success`, `warning`, `error`. - **after** (string) - Optional - Alert ID. Returns alerts strictly newer. ### Response #### Success Response (200) - **alerts** (array) - List of alert objects. - **nextCursor** (string) - Cursor for the next page. - **hasMore** (boolean) - Indicates if more pages are available. ``` -------------------------------- ### GET /api/alerts/{id} Source: https://zenhook.dev/docs/api/read Retrieves a single alert by its ID, returning the alert shape along with the channel's name and slug. ```APIDOC ## GET /api/alerts/{id} ### Description Returns the same alert shape as the list endpoint plus the channel's name and slug. ### Method GET ### Endpoint /api/alerts/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the alert. ### Errors - **401 Unauthorized**: Missing or invalid Bearer token, or the token's user is no longer a workspace member. - **404 Not Found**: The resource doesn't exist or the token's user can't access it. - **429 Too Many Requests**: Rate limit hit. ``` -------------------------------- ### Configure Callback Mode Source: https://zenhook.dev/docs/ask Request an ask without blocking by providing a callback URL for the outcome. ```json { "title": "Refund $420 to customer #1847?", "actions": ["Refund", "Escalate", "Ignore"], "timeout": 3600, "wait": false, "callbackUrl": "https://your-app.com/hooks/zenhook-answer" } ``` -------------------------------- ### Test Zenhook channel with curl Source: https://zenhook.dev/docs/recipes/dependabot Use this command to verify that your Zenhook channel is correctly configured by sending a sample alert. ```bash curl -X POST "YOUR_ZENHOOK_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{ "title": "Bump lodash from 4.17.20 to 4.17.21", "level": "info", "emoji": "package", "source": "Dependabot", "message": "Dependency update proposed in acme/api", "linkUrl": "https://github.com/acme/api/pull/42", "linkText": "View PR" }' ``` -------------------------------- ### Send an alert using fetch Source: https://zenhook.dev/docs Integrate Zenhook into your application using a standard fetch request without needing an SDK. ```typescript await fetch("https://zenhook.dev/api/webhook/YOUR_TOKEN", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: "New signup!", level: "success", emoji: "🎉", fields: [ { label: "Email", value: "jane@example.com" }, { label: "Plan", value: "Pro" } ] }) }); ``` -------------------------------- ### Send a test alert via cURL Source: https://zenhook.dev/docs Use this command to verify your webhook URL from the terminal. ```bash curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -H "Content-Type: application/json" \ -d '{ "title": "Zenhook is working!", "level": "success", "emoji": "🚀", "message": "First alert sent from terminal." }' ``` -------------------------------- ### Implement Approval Gate with Polling Source: https://zenhook.dev/docs/ask Use a bash loop to poll the status of an ask request until a decision is made. ```bash RES=$(curl -s -X POST "https://zenhook.dev/api/ask/$TOKEN" \ -H 'Content-Type: application/json' \ -d '{"title":"Deploy to prod?","actions":["Deploy","Abort"],"timeout":600}') while [ "$(echo "$RES" | jq -r .status)" = "pending" ]; do RES=$(curl -s "$(echo "$RES" | jq -r .pollUrl)?wait=60") done if [ "$(echo "$RES" | jq -r .answer)" = "Deploy" ]; then ./deploy.sh else echo "Held back by a human." && exit 1 fi ``` -------------------------------- ### Attach files by URL Source: https://zenhook.dev/docs/attachments Provide a public URL in the attachments array. Zenhook fetches and re-hosts the file to ensure persistence. ```curl curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -H "Content-Type: application/json" \ -d '{ "title": "Build failed", "level": "error", "attachments": [ { "url": "https://ci.example.com/runs/123/screenshot.png" } ] }' ``` -------------------------------- ### Send an alert with a file attachment Source: https://zenhook.dev/docs/api Use multipart/form-data to upload files directly alongside the alert payload. ```bash curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -F 'payload={"title":"Build failed","level":"error"}' \ -F 'file=@./screenshot.png' ``` -------------------------------- ### POST /api/webhook/{YOUR_TOKEN} (Multipart) Source: https://zenhook.dev/docs/attachments Send a webhook alert with files uploaded directly as multipart/form-data. ```APIDOC ## POST /api/webhook/{YOUR_TOKEN} ### Description Send a webhook alert with files uploaded directly as multipart/form-data. This is used when files are not publicly accessible. ### Method POST ### Endpoint https://zenhook.dev/api/webhook/{YOUR_TOKEN} ### Parameters #### Path Parameters - **YOUR_TOKEN** (string) - Required - The unique token for your webhook. #### Request Body - **payload** (string) - Optional - JSON string containing webhook fields (title, level, etc.). - **file** (binary) - Optional - The file content to upload. ### Request Example curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -F 'payload={"title":"Visual diff","level":"warning"};type=application/json' \ -F 'before=@before.png' \ -F 'after=@after.png' ``` -------------------------------- ### Send Workflow Notification Source: https://zenhook.dev/docs/api Use a consistent runId to update a single alert card with live step progress. The server replaces the entire steps array with each request. ```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" } ] } }' ``` -------------------------------- ### Send an Ask Request Source: https://zenhook.dev/docs/ask Initiate a human-in-the-loop request using cURL. The request blocks until an answer is provided or the timeout is reached. ```bash curl -X POST https://zenhook.dev/api/ask/abc123def456 \ -H "Content-Type: application/json" \ -d '{ "title": "Deploy api-server to production?", "message": "3 commits since the last deploy. Tests green.", "actions": ["Deploy", "Abort"], "default": "Abort", "timeout": 600 }' ``` -------------------------------- ### POST /api/webhook/{YOUR_TOKEN} (Base64) Source: https://zenhook.dev/docs/attachments Send a webhook alert with files encoded as base64 within a JSON payload. ```APIDOC ## POST /api/webhook/{YOUR_TOKEN} ### Description Send a webhook alert with files encoded as base64. This is intended for SaaS platforms that only support JSON payloads. ### Method POST ### Endpoint https://zenhook.dev/api/webhook/{YOUR_TOKEN} ### Parameters #### Path Parameters - **YOUR_TOKEN** (string) - Required - The unique token for your webhook. #### Request Body - **title** (string) - Required - The title of the alert. - **attachments** (array) - Optional - A list of attachment objects. - **data** (string) - Required - The base64 encoded file bytes. - **name** (string) - Required - The filename for the attachment. ### Request Example { "title": "Visual diff", "attachments": [ { "data": "iVBORw0KGgoAAAANSUhEUg...", "name": "diff.png" } ] } ``` -------------------------------- ### POST /api/webhook/{YOUR_TOKEN} Source: https://zenhook.dev/docs/attachments Send a webhook alert with attachments provided via public URLs. ```APIDOC ## POST /api/webhook/{YOUR_TOKEN} ### Description Send a webhook alert with attachments provided via public URLs. Zenhook fetches the file once and re-hosts it. ### Method POST ### Endpoint https://zenhook.dev/api/webhook/{YOUR_TOKEN} ### Parameters #### Path Parameters - **YOUR_TOKEN** (string) - Required - The unique token for your webhook. #### Request Body - **title** (string) - Required - The title of the alert. - **level** (string) - Optional - The severity level of the alert. - **attachments** (array) - Optional - A list of attachment objects. - **url** (string) - Required - The public URL of the file. - **type** (string) - Optional - A hint for the file type. ### Request Example { "title": "Build failed", "level": "error", "attachments": [ { "url": "https://ci.example.com/runs/123/screenshot.png" } ] } ``` -------------------------------- ### POST /api/ask/{token} Source: https://zenhook.dev/docs/ask Sends a question to a channel. The call blocks until a human answers or the timeout is reached, unless wait is set to false. ```APIDOC ## POST /api/ask/{token} ### Description Sends a question to a channel. By default, the call blocks until a human answers or approximately 80 seconds pass. ### Method POST ### Endpoint https://zenhook.dev/api/ask/{token} ### Parameters #### Path Parameters - **token** (string) - Required - The webhook token for your channel. #### Request Body - **title** (string) - Required - The question to ask. - **actions** (string[]) - Optional - 1-6 short choices. Defaults to ["Approve", "Deny"]. - **message** (string) - Optional - Context under the title. - **fields** (array) - Optional - Label/value pairs shown on the answer page. - **timeout** (number) - Optional - Seconds until the ask expires (30 to 86400). Default 600. - **default** (string) - Optional - Suggested fallback if nobody answers. - **wait** (bool | number) - Optional - true (default) holds the call; false returns 201 immediately with a pollUrl. - **callbackUrl** (string) - Optional - URL to POST the outcome to upon answer or expiry. - **emoji** (string) - Optional - Emoji for the alert. - **level** (string) - Optional - Alert level. - **source** (string) - Optional - Source identifier. ### Request Example { "title": "Deploy api-server to production?", "message": "3 commits since the last deploy. Tests green.", "actions": ["Deploy", "Abort"], "default": "Abort", "timeout": 600 } ### Response #### Success Response (200) - **id** (string) - Unique identifier for the ask. - **status** (string) - Current status (e.g., "answered"). - **answer** (string) - The selected action. - **answeredVia** (string) - The surface used to answer. - **answeredAt** (string) - Timestamp of the answer. - **actions** (string[]) - The available actions. - **defaultAction** (string) - The default action provided. - **askUrl** (string) - URL to the public answer page. - **pollUrl** (string) - URL to poll for the status. ### Response Example { "id": "ask_cmr5o5l6", "status": "answered", "answer": "Deploy", "answeredVia": "link", "answeredAt": "2026-07-04T09:41:12.000Z", "actions": ["Deploy", "Abort"], "defaultAction": "Abort", "askUrl": "https://zenhook.dev/ask/…", "pollUrl": "https://zenhook.dev/api/ask/…/ask_cmr5o5l6" } ``` -------------------------------- ### Authenticate API Requests Source: https://zenhook.dev/docs/api/read Use the Authorization header with a Bearer token to authenticate requests. ```bash curl https://zenhook.dev/api/channels \ -H "Authorization: Bearer zhk_..." ``` -------------------------------- ### List channel alerts Source: https://zenhook.dev/docs/api/read Retrieves a paginated list of alerts for a specific channel using its slug. ```bash curl https://zenhook.dev/api/channels/deploys/alerts \ -H "Authorization: Bearer zhk_..." ``` -------------------------------- ### POST /api/webhook/{token} Source: https://zenhook.dev/docs/api Sends a workflow notification to Zenhook. Using the workflow block with a consistent runId allows for in-place updates of a single alert card. ```APIDOC ## POST /api/webhook/{token} ### Description Sends a workflow notification. If a workflow block with a runId is provided, subsequent POSTs with the same runId will update the existing alert card instead of creating new ones. ### Method POST ### Endpoint https://zenhook.dev/api/webhook/{token} ### Parameters #### Path Parameters - **token** (string) - Required - Your unique webhook token. #### Request Body - **title** (string) - Required - The title of the notification. - **workflow** (object) - Optional - Workflow tracking information. - **runId** (string) - Required - Unique identifier for the workflow process. - **state** (string) - Required - Current state of the run (running, passed, or failed). - **steps** (array) - Required - Array of step objects (name, state, seconds, info). - **error** (string) - Optional - Log tail or error message for failed runs. - **fields** (array) - Optional - Metadata fields for display (label, value). - **attachments** (array) - Optional - Media attachments (url, type). - **linkUrl** (string) - Optional - URL for the alert link. - **linkText** (string) - Optional - Text for the alert link. ### Request Example { "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" } ] } } ``` -------------------------------- ### Attach files using base64 encoding Source: https://zenhook.dev/docs/attachments Use base64 encoding for SaaS platforms that only support JSON payloads. Note that this increases payload size by approximately 33%. ```curl curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -H "Content-Type: application/json" \ -d '{ "title": "Visual diff", "attachments": [ { "data": "iVBORw0KGgoAAAANSUhEUg...", "name": "diff.png" } ] }' ``` -------------------------------- ### GitHub Actions workflow for Dependabot notifications Source: https://zenhook.dev/docs/recipes/dependabot This workflow file triggers on Dependabot pull requests and sends metadata to Zenhook using the stored secret. ```yaml name: Zenhook - Dependabot alerts on: pull_request: types: [opened, reopened] permissions: contents: read jobs: notify: if: github.event.pull_request.user.login == 'dependabot[bot]' runs-on: ubuntu-latest steps: - name: Send the pull request to Zenhook env: ZENHOOK_WEBHOOK_URL: ${{ secrets.ZENHOOK_WEBHOOK_URL }} PR_TITLE: ${{ github.event.pull_request.title }} PR_URL: ${{ github.event.pull_request.html_url }} PR_NUMBER: ${{ github.event.pull_request.number }} REPOSITORY: ${{ github.repository }} run: | jq -n \ --arg title "$PR_TITLE" \ --arg url "$PR_URL" \ --arg number "$PR_NUMBER" \ --arg repository "$REPOSITORY" \ '{ title: $title, level: "info", emoji: "package", source: "Dependabot", message: ("Dependency update proposed in " + $repository), fields: [ { label: "Repository", value: $repository }, { label: "Pull request", value: ("#" + $number) } ], linkUrl: $url, linkText: "View PR" }' | curl --fail-with-body --retry 2 \ -X POST "$ZENHOOK_WEBHOOK_URL" \ -H "Content-Type: application/json" \ --data-binary @- ``` -------------------------------- ### Fetch Filtered Feedback Source: https://zenhook.dev/docs/api/read Retrieve alerts and filter for member-authored feedback using curl and jq. ```bash curl -s "https://zenhook.dev/api/channels/feedback/alerts?limit=50" \ -H "Authorization: Bearer zhk_..." \ | jq '[.alerts[] | select(.author.role == "member")]' ``` -------------------------------- ### POST /api/webhook/{token} Source: https://zenhook.dev/docs/api Trigger a new alert in a specific channel by sending a JSON payload to the webhook URL. ```APIDOC ## POST /api/webhook/{token} ### Description Trigger a new alert in a specific channel. This is the primary endpoint for sending notifications from your application. ### Method POST ### Endpoint https://zenhook.dev/api/webhook/{token} ### Parameters #### Path Parameters - **token** (string) - Required - Your unique channel webhook token. Find it in channel settings. #### Request Body - **title** (string) - Required - A brief summary of the alert. Used as the headline in notifications. Max 200 characters. - **level** (enum) - Optional - Severity of the alert: `info`, `success`, `warning`, `error`. Defaults to `info`. - **message** (string) - Optional - Detailed body text. Max 5,000 characters. - **emoji** (string) - Optional - Custom emoji displayed next to the title. - **fields** (array) - Optional - Key-value objects for structured data. Each object has `label` and `value`. - **linkUrl** (string) - Optional - URL for a clickable call-to-action link displayed below the title. - **linkText** (string) - Optional - Label for the link button. Defaults to "View" if `linkUrl` is set. - **attachments** (array) - Optional - Up to 8 media items shown inside the alert. Each object takes a `url` plus an optional `type` (`image`, `video`, or `audio`) and `name` caption. - **source** (string) - Optional - Author name shown in the alert header. Defaults to "Webhook". - **metadata** (object) - Optional - Arbitrary JSON object shown as an expandable "Raw Payload" section. Max 10 KB. ### Request Example ```json { "title": "New User Signup", "level": "success", "emoji": "🎉", "source": "My App", "message": "jane.doe@example.com just joined your workspace.", "linkUrl": "https://myapp.com/admin/users/42", "linkText": "View Profile" } ``` ### Response #### Success Response (201) - **success** (boolean) - Indicates if the request was successful. - **alertId** (string) - Unique identifier for the created alert. - **queuedAt** (string) - Timestamp when the alert was queued. #### Response Example ```json { "success": true, "alertId": "clx1234567890abcdef", "queuedAt": "2024-03-22T12:00:00Z" } ``` ``` -------------------------------- ### Handle Ask Response Source: https://zenhook.dev/docs/ask The JSON structure returned by the API once a human has provided an answer. ```json { "id": "ask_cmr5o5l6", "status": "answered", "answer": "Deploy", "answeredVia": "link", "answeredAt": "2026-07-04T09:41:12.000Z", "actions": ["Deploy", "Abort"], "defaultAction": "Abort", "askUrl": "https://zenhook.dev/ask/…", "pollUrl": "https://zenhook.dev/api/ask/…/ask_cmr5o5l6" } ``` -------------------------------- ### Mark alert as read Source: https://zenhook.dev/docs/api/read Updates the read status of a specific alert for the authenticated user. ```bash curl -X PATCH https://zenhook.dev/api/alerts/clx_alert_1 \ -H "Authorization: Bearer zhk_..." \ -H "Content-Type: application/json" \ -d '{ "isRead": true }' ``` -------------------------------- ### Notification preference lookup order Source: https://zenhook.dev/docs/permissions The order in which the system evaluates notification settings, where the first non-null value takes precedence. ```text 1. Channel preference (user × channel) 2. Workspace preference (user × workspace) 3. User default (user) ``` -------------------------------- ### POST /api/alerts/mark-all-read Source: https://zenhook.dev/docs/api/read Marks all alerts as read, optionally scoped to a specific channel. ```APIDOC ## POST /api/alerts/mark-all-read ### Description Marks every accessible alert as read. This operation is idempotent. ### Method POST ### Endpoint https://zenhook.dev/api/alerts/mark-all-read ### Request Body - **channelId** (string) - Optional - Scope the operation to a specific channel. If omitted, marks all accessible alerts as read. ``` -------------------------------- ### Define Structured Data Fields Source: https://zenhook.dev/docs/api Include rich metadata in alerts using the fields array and provide external links or attachments for better context. ```json { "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" } ``` -------------------------------- ### API success response Source: https://zenhook.dev/docs/api The JSON structure returned upon a successful 201 Created response. ```json { "success": true, "alertId": "clx1234567890abcdef", "queuedAt": "2024-03-22T12:00:00Z" } ``` -------------------------------- ### Discord Alert Object Structure Source: https://zenhook.dev/docs/api/read The schema for alert objects retrieved from Discord-connected channels. ```json { "id": "clx_alert_1", "message": "Le fait de pouvoir activer finement les notifications...", "createdAt": "2026-07-19T17:29:14.439Z", "author": { "id": "1108...", "name": "Heri", "bot": false, "role": "member" }, "replyTo": { "messageId": "1528...", "author": "Ayk", "excerpt": "..." }, "attachments": [{ "url": "https://cdn.discordapp.com/...", "mime": "image/png" }] } ``` -------------------------------- ### Send a JSON alert via cURL Source: https://zenhook.dev/docs/api Trigger a notification by sending a JSON payload to the webhook endpoint. ```bash 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": "jane.doe@example.com just joined your workspace.", "linkUrl": "https://myapp.com/admin/users/42", "linkText": "View Profile" }' ``` -------------------------------- ### PATCH /api/alerts/{id} Source: https://zenhook.dev/docs/api/read Marks a specific alert as read for the authenticated user. ```APIDOC ## PATCH /api/alerts/{id} ### Description Marks an individual alert as read. This state is per-user and does not affect other workspace members. ### Method PATCH ### Endpoint https://zenhook.dev/api/alerts/{id} ### Request Body - **isRead** (boolean) - Required - Set to true to mark the alert as read. ``` -------------------------------- ### Upload files via multipart/form-data Source: https://zenhook.dev/docs/attachments Send file bytes directly when no public URL is available. Use a payload part for JSON metadata and separate parts for each file. ```curl curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -F 'title=Build failed' \ -F 'level=error' \ -F 'file=@screenshot.png' ``` ```curl curl -X POST https://zenhook.dev/api/webhook/YOUR_TOKEN \ -F 'payload={"title":"Visual diff","level":"warning","fields":[{"label":"Suite","value":"checkout"}]};type=application/json' \ -F 'before=@before.png' \ -F 'after=@after.png' ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.