Incoming webhooks
An incoming webhook is a secret URL that posts into one channel. It speaks Discord's webhook format, so anything that can "notify Discord" works without changes: CI servers, uptime monitors, server panels, n8n, Zapier, Make and your own scripts. The same URL also takes Slack's format, and GitHub can send its events to it.
Make one
- In Missived, open Org Settings → Integrations → Incoming webhooks → Add incoming webhook. You need the Manage integrations permission (Owners, Admins, or a custom role that grants it).
- Give it a name (shown on its messages with an APP badge), pick the channel it posts into, and optionally an avatar (an https PNG, JPEG, GIF or WebP).
- Copy the URL. It is shown once: Missived keeps only a hash of it. If you lose it or it leaks, choose Regenerate URL; the old URL stops working at once.
You can also make and manage webhooks over the API.
The three URLs
| URL | Speaks | Use it for |
|---|---|---|
…/hooks/<id>/<token> |
Discord's execute-webhook JSON | Anything with a "Discord webhook" option, curl, scripts |
…/hooks/<id>/<token>/slack |
Slack's incoming-webhook JSON | Tools that only know Slack |
…/hooks/<id>/<token>/github |
GitHub webhook events | A repository's or organisation's webhook settings |
Discord format
curl -H "Content-Type: application/json" \
-d '{"content": "Deploy finished", "username": "Deploys",
"embeds": [{"title": "api v1.4.2", "description": "All checks passed", "color": 3066993}]}' \
'https://api.missived.com/hooks/<id>/<token>'
content: up to 2000 characters of Markdown.usernameandavatar_url(https) change how this one message's author looks.embeds: up to 10 cards, with Discord's limits (title 256 characters, description 4096, 25 fields, 6000 characters in all). Links and images that aren't https are dropped from a card rather than refusing the post. Images are shown through Missived's image proxy.allowed_mentionsdecides who gets notified. Without it,@personmentions notify, and@everyone,@hereand role mentions don't.{"parse": ["everyone"]}(or"roles") turns those on, but only if the person who made the webhook may use them.{"parse": []}notifies nobody.flags:4skips link previews;4096skips push notifications.?wait=trueanswers with the created message (Discord's shape) instead of204 No Content.?thread_id=<message id>posts as a reply in that message's thread.thread_name,tts,componentsandpollare accepted and ignored.multipart/form-datawith apayload_jsonpart works, but file uploads are refused with400(code40005). Link to the file instead, or use the API's file upload.
Errors use Discord's shapes, such as {"code": 50035, "message": "Invalid Form Body", "errors": …}, 10015 Unknown Webhook and 50027 Invalid Webhook Token, so tools report them sensibly.
Slack format
Post Slack's JSON to the /slack URL:
textin Slack's mrkdwn (*bold*,<url|label>and so on).blocks: header, section, fields, context, divider, image, link buttons and rich text.- Legacy
attachments: colour, title, fields, images and footer become cards. usernameandicon_urlset the author.<!here>and<!channel>show as text and notify nobody.
It answers ok, as Slack does.
GitHub
In the repository, open Settings → Webhooks → Add webhook:
- Payload URL: the
/githubURL. - Content type:
application/json. - Secret: the GitHub secret shown with the URL (or a new one from New GitHub secret).
Missived checks GitHub's signature on every delivery and refuses unsigned or wrongly signed ones. A redelivery of the same body within 24 hours is answered duplicate and not posted twice.
These events become cards: ping (the "it's connected" card), pushes (up to five commits), branch and tag creation and deletion, pull requests opened, closed, merged, reopened or marked ready for review, issues opened, closed or reopened, issue and pull request comments, published releases, and finished workflow runs. Other events are answered 204 and ignored.
Examples
Uptime Kuma: Settings → Notifications → Setup Notification → type Discord → paste the URL as the Discord Webhook URL → Test.
GitHub Actions: save the URL as a repository secret named MISSIVED_WEBHOOK, then add a step:
- name: Tell Missived
if: always()
env:
MISSIVED_WEBHOOK: ${{ secrets.MISSIVED_WEBHOOK }}
TITLE: "${{ github.workflow }}: ${{ job.status }}"
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
run: |
jq -n --arg t "$TITLE" --arg u "$RUN_URL" '{embeds: [{title: $t, url: $u}]}' |
curl -sS -H "Content-Type: application/json" -d @- "$MISSIVED_WEBHOOK"
n8n, Zapier, Make: use their Discord node or action and paste the URL.
Limits
- Per webhook: 5 requests per 2 seconds and 30 a minute. Over that, you get
429withretry_afterand theRetry-AfterandX-RateLimit-*headers. - Per address: 300 requests a minute to any webhook URL. After 30 wrong tokens or GitHub signatures in 10 minutes, that address gets
429instead. - A webhook that stays over its limit for an hour pauses itself, and its creator gets an email. Resume it on the Integrations page.
- Bodies are capped at 1 MiB.
- Each plan allows a number of webhooks per org; the list operation returns it as
cap.
Messages from a webhook are stored like any other message: searchable, encrypted at rest where the org encrypts content, and they notify the people they mention.
Manage webhooks over the API
The same management the Integrations page does is available to a token with the webhooks:write scope:
| Operation | Does |
|---|---|
GET /v1/orgs/{orgId}/incoming-webhooks |
List webhooks, with the plan's cap |
POST /v1/orgs/{orgId}/incoming-webhooks |
Make one: { "name", "channelId", "avatarUrl" } |
PATCH /v1/orgs/{orgId}/incoming-webhooks/{id} |
Rename, move, change the avatar, or pause / resume ("paused": true) |
POST /v1/orgs/{orgId}/incoming-webhooks/{id}/rotate |
Issue a new URL; the old one stops working |
POST /v1/orgs/{orgId}/incoming-webhooks/{id}/github-secret |
Issue a new GitHub secret |
GET /v1/orgs/{orgId}/incoming-webhooks/{id}/deliveries |
Recent posts and refusals (statuses only, never content) |
DELETE /v1/orgs/{orgId}/incoming-webhooks/{id} |
Delete it; its old messages stay, with their APP badge |
curl -X POST https://api.missived.com/v1/orgs/<orgId>/incoming-webhooks \
-H "Authorization: Bearer $MISSIVED_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Deploys", "channelId": "<channelId>"}'
The answer (201) holds the webhook, its url and path, and githubSecret. The URL and the GitHub secret are returned only here and by rotate and github-secret; store them right away.
Who may do what:
- A personal token needs
webhooks:write, which only an Owner or Admin can give a token, and its owner needs the Manage integrations permission in the org. It sees and changes every webhook in the org, as the Integrations page does. - An app token can point webhooks only at channels its app was added to, and sees only the webhooks its own install made. Those webhooks are recorded as made by the person who set the app up; that person's permissions decide whether
@everyonecan be used.
These routes are older than the rest of /v1: some field names are camelCase (channelId and avatarUrl in requests, githubSecret in answers), lists answer { "webhooks": [...], "cap" } rather than data, and errors answer { "error": "A sentence.", "code": "…" } rather than the usual { "error": { "code", "message" } }. Every change appears in the org's Audit log.