DiyorDevelopers

Incoming webhooks

A webhook is a secret URL that posts into one text channel. Anything that can send an HTTP request (a CI job, a monitoring alert, a cron script, GitHub, a tool written for Discord or Slack) can write messages, embeds and files there. No bot, no token and no library needed. Messages show the webhook’s name and picture with a BOT label. Base URL: https://app.diyor.chat/api/v1

MethodPathDoes
POST/webhooks/{id}/{token}Send a message (JSON, form or multipart with files).
GET/webhooks/{id}/{token}The webhook: name, channel, server.
GET/webhooks/{id}/{token}/messages/{message}A message this webhook sent.
PATCH/webhooks/{id}/{token}/messages/{message}Edit its content or embeds.
DELETE/webhooks/{id}/{token}/messages/{message}Delete it (its files too).
POST/webhooks/{id}/{token}/githubGitHub events, written as readable messages.

Create a webhook

  1. In the Diyor app open Server settings → Integrations (Sunucu ayarları → Entegrasyonlar). You need the Manage server permission.
  2. Click Create webhook (Webhook oluştur). Copy the URL shown; it looks like https://app.diyor.chat/api/v1/webhooks/{id}/{token}. There is also a button that copies the GitHub variant.
  3. In the list, give the webhook a name and a picture and pick the channel its messages go to. Announcement channels cannot have webhooks.

From the same list you can get a new URL (Yeni adres; the old one stops working) or delete the webhook. A server can have up to 15 webhooks.

Send a message

curl -X POST "https://app.diyor.chat/api/v1/webhooks/{id}/{token}" \
  -H "Content-Type: application/json" \
  -d '{"content": "Nightly backup completed ✅"}'
-> 204 No Content

JavaScript (Node.js 18+)

await fetch(process.env.DIYOR_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ content: 'Deploy finished', username: 'Deploy bot' }),
});

Python

import os, requests

requests.post(os.environ["DIYOR_WEBHOOK_URL"], json={"content": "Disk usage over 90% on db-1"}).raise_for_status()

The body can also be form data with a payload field holding the JSON (as Slack sends it).

Message fields

FieldTypeMeaning
contentstringThe message text, up to 2000 characters. Markdown like any message. Alias: text (Slack).
usernamestringShown as the author for this message only. Up to 40 characters; cannot be “Diyor”.
avatar_urlstringPicture for this message only: an https URL, up to 500 characters.
embedsarrayUp to 3 embeds (see below).

At least one of content, embeds or a file is required. Unknown fields are ignored.

{
  "content": "Release **v2.4.0** is out",
  "username": "Release notes",
  "avatar_url": "https://example.com/logo.png"
}

Embeds

Embeds are cards with a colored edge. Up to 3 per message; all their text together at most 6000 characters.

{
  "embeds": [{
    "title": "Build #128 passed",                 // 256
    "description": "All **42** tests passed",     // 4000, Markdown
    "url": "https://ci.example.com/128",          // https only
    "color": 3066993,                             // or "#2ecc71"
    "fields": [                                   // up to 10; name 256, value 1024
      { "name": "Branch", "value": "main", "inline": true },
      { "name": "Duration", "value": "3m 12s", "inline": true }
    ],
    "image": { "url": "https://example.com/coverage.png" },
    "footer": { "text": "CI" }                    // 2048
  }]
}

Every embed field and its limit: Embed object.

Files

To attach files, send multipart/form-data: the message as JSON in a payload_json part and each file in its own part named files[0], files[1] … files[9] (file, file1 … also work). Each file keeps its name and type.

curl "https://app.diyor.chat/api/v1/webhooks/{id}/{token}" \
  -F 'payload_json={"content": "Weekly report"}' \
  -F 'files[0]=@report.pdf' \
  -F 'files[1]=@chart.png'

JavaScript (Node.js 20+)

import { openAsBlob } from 'node:fs';

const form = new FormData();
form.append('payload_json', JSON.stringify({ content: 'Weekly report' }));
form.append('files[0]', await openAsBlob('report.pdf', { type: 'application/pdf' }), 'report.pdf');
await fetch(process.env.DIYOR_WEBHOOK_URL, { method: 'POST', body: form });

Python

import json, os, requests

with open("report.pdf", "rb") as pdf, open("chart.png", "rb") as png:
    requests.post(os.environ["DIYOR_WEBHOOK_URL"], data={"payload_json": json.dumps({"content": "Weekly report"})},
                  files={"files[0]": ("report.pdf", pdf, "application/pdf"), "files[1]": ("chart.png", png, "image/png")}).raise_for_status()
  • Up to 10 files per message and 100 MB per request. The request needs a Content-Length header (curl, fetch and requests send it).
  • Files belong to the server owner: the size of each file, the daily upload quota and how long files are kept follow the owner’s plan, as if they had uploaded them. Per file: 25 MB on Diyor, 250 MB on Pro, 1 GB on Pro Max.
  • Members of the server see and download the files in the channel; a bot with message content access gets them in attachments.
  • All files are checked before any is stored. If one is rejected, or the message cannot be sent, nothing is kept and nothing counts against the quota.
  • Diyor 1.7.81 and later show every file. Older apps show the first one.

Get the message back

By default a successful post answers 204 No Content. Add ?wait=true to get 200 with the created message; keep its id to edit or delete it later.

POST /webhooks/{id}/{token}?wait=true
-> 200
{
  "id": "5c8d12d6-…", "server_id": "622rfdt5f7uj", "channel_id": "general",
  "author": { "id": "qoha6xse1sorpi4m", "name": "Deploy bot", "avatar_url": "https://example.com/logo.png", "webhook": true },
  "content": "Weekly report", "timestamp": "2026-10-22T10:19:51.095Z", "edited_timestamp": null,
  "attachments": [{ "id": "9f1c…", "name": "report.pdf", "filename": "report.pdf", "size": 52311, "content_type": "application/pdf", "url": "…" }],
  "embeds": [], "reactions": [], "pinned": false, "components": [], "reply_to": null, "interaction": null
}

Edit and delete

A webhook can read, edit and delete only the messages it sent itself.

GET    /webhooks/{id}/{token}/messages/{message_id}      -> 200 message
PATCH  /webhooks/{id}/{token}/messages/{message_id}      -> 200 message
       { "content": "Build #128 passed (re-run)", "embeds": [] }
DELETE /webhooks/{id}/{token}/messages/{message_id}      -> 204
  • PATCH takes JSON with content and/or embeds; a field you leave out stays as it is, [] removes the embeds. Files cannot be changed. The message shows “edited”.
  • DELETE removes the message and its files for everyone.

A status message that you keep updating (a deploy in progress, a long job) is a good use: post with ?wait=true, then PATCH the same id.

The webhook itself

GET /webhooks/{id}/{token}
-> 200
{ "id": "qoha6xse1sorpi4m", "type": 1, "name": "Deploy bot", "avatar": null, "avatar_url": "https://app.diyor.chat/resim/w/…",
  "channel_id": "general", "guild_id": "622rfdt5f7uj", "application_id": null, "token": "…" }

Handy to check a URL before saving it. guild_id is the server id (the name Discord tools expect).

Coming from Discord

Most tools that post to Discord webhooks work unchanged: replace the start of the URL.

https://discord.com/api/webhooks/{id}/{token}
https://app.diyor.chat/api/webhooks/{id}/{token}        // /api/v10/webhooks/… works too
DiscordDiyor
content, username, avatar_url, embedsSupported
files[n] + payload_jsonSupported: 10 files, 100 MB per request, per-file size by the owner’s plan
?wait=true, GET/PATCH/DELETE messages/{id}, GET webhookSupported
attachment://nameNot supported: embed images that point to an attached file are dropped (the file itself is sent)
thread_id, thread_nameIgnored: the message goes to the webhook’s channel
tts, allowed_mentions, flags, components, pollIgnored
Rate limit headers (X-RateLimit-*)Only Retry-After on 429

Slack and GitHub

  • Slack: {"text": "…"} is accepted on the same URL, also as payload= form data. Many Slack integrations can post to Diyor this way.
  • GitHub: in the repository go to Settings → Webhooks → Add webhook, use …/{token}/github as the payload URL and application/json as the content type. Diyor turns pushes, pull requests (opened, merged, closed, ready for review), reviews, issues, comments, releases, stars, forks and finished workflow runs into short messages; other events are accepted and skipped.

Limits

WhatLimit
Messages30 per minute per webhook
content2000 characters
username40 characters
avatar_urlhttps, 500 characters
Embeds3 per message, 6000 characters in total
JSON or form body1 MB
Files10 per message, 100 MB per request
Size of one fileServer owner’s plan: 25 MB (Diyor), 250 MB (Pro), 1 GB (Pro Max)
Daily upload quota and how long files stayServer owner’s plan, shared with the owner’s own uploads
Webhooks15 per server

Errors

Errors are JSON: {"code": "rate_limited", "message": "You are being rate limited."}. On 429 and 503 wait the seconds in Retry-After before trying again.

StatuscodeWhen
400invalid_jsonThe body (or payload_json) is not a JSON object.
400invalid_formThe multipart body could not be read.
400empty_messageNo content, embeds or files.
400content_too_longcontent is over 2000 characters.
400invalid_embedsAn embed breaks a rule; message says which.
400invalid_avatar_urlavatar_url is not an https URL of at most 500 characters.
400too_many_filesMore than 10 files.
404unknown_webhookWrong id or token, or the webhook was deleted or got a new URL.
404unknown_channelThe webhook’s channel no longer exists.
404unknown_messageNo such message, it was deleted, or another author sent it.
405method_not_allowedWrong method for this path (see the Allow header).
411length_requiredA request with files has no Content-Length.
413payload_too_largeJSON body over 1 MB or a request with files over 100 MB.
413file_too_largeA file is over the per-file limit of the server owner’s plan.
429rate_limitedOver 30 messages a minute. Wait Retry-After seconds.
429upload_quota_exceededThe server owner’s daily upload quota is used up.
502upload_failedA file could not be stored. Nothing was sent; try again.
503storage_unavailableLarge files cannot be stored right now. Retry after Retry-After.
507storage_fullThe file storage is full.

Security

  • The URL is the password: anyone who has it can post to the channel. Keep it in a secret store or an environment variable, not in code or public repositories.
  • If it leaks, create a new URL for it in Server settings → Integrations (Yeni adres). The old URL stops working at once.
  • Diyor stores only a hash of the token, so a lost URL cannot be shown again; create a new one instead.
  • A webhook can only post to its own channel and touch its own messages.

Bots, slash commands and the full API: bot API documentation. Machine-readable API: openapi.json.