Repository navigation
Delivery
delivery tells Ordryn how to POST when a hook fires.
| Field | Required | Notes |
|---|---|---|
type |
yes | One of the types below. |
url_from |
yes | Must be a settings key (almost always a secret). |
format |
no | Only for http.webhook. One of json, text, or content. Defaults to text ({"text": "…"}) if omitted. Rejected on provider-specific types. |
Every delivery is POST with Content-Type: application/json, a 5s timeout, and no redirects. Interpolated message text is trimmed to 2000 characters; @everyone / @here are stripped.
Outbound headers:
| Header | When |
|---|---|
User-Agent: Ordryn-Webhook/1 |
Always |
X-Ordryn-Event-Id |
Always (event id) |
X-Ordryn-Delivery-Id |
Queued deliveries |
X-Ordryn-Signature: sha256=<hex> |
After rotate_signing; HMAC-SHA256 of the raw JSON body
|
Project owners can send a test post from Project settings → Extensions (bypasses enable/trigger filters; still needs a webhook URL). That control is send_test. See Settings and controls.
Prefer the dedicated Discord / Slack / Teams / Google Chat / ntfy examples when targeting those services: they send the payload the service expects and restrict the URL host. Use http.webhook for everything else. delivery.type: "email" is rejected (site email is not available to extensions).
delivery.type |
Use for | Body |
|---|---|---|
http.webhook |
Any public HTTPS receiver (n8n, Zapier, Mattermost, custom listeners, email relays) | Chosen by format
|
discord.webhook |
Discord incoming webhooks |
{"content": "…"} plus a small embed |
slack.webhook |
Slack incoming webhooks |
{"text": "…"} plus Block Kit with an Open button |
teams.webhook |
Teams Workflows incoming webhooks (Office 365 Connectors are retired) | Adaptive Card |
googlechat.webhook |
Google Chat incoming webhooks | Chat text plus cardsV2 (generic extra JSON fields are rejected by Chat) |
ntfy.webhook |
ntfy topic URL (ntfy.sh or a public HTTPS ntfy server) |
ntfy push; optional Authorization: Bearer from ntfy_auth
|
format |
Body |
|---|---|
json |
Structured event (below). Includes text and content plus event fields. |
text |
{"text": "…"} (Slack-style). Default if format is omitted. |
content |
{"content": "…"} (Discord-style). |
From the webhook example:
{
"text": "Task Ship updated to Done in project Ordryn",
"content": "Task Ship updated to Done in project Ordryn",
"event": "task.updated",
"event_id": "00000000-0000-0000-0000-000000000001",
"occurred_at": "2026-09-15T16:00:00Z",
"changed": ["status"],
"id": "42",
"name": "Ship",
"task": "Ship",
"status": "Done",
"old_status": "In progress",
"project": "Ordryn",
"actor": "ada",
"url": "https://todo.example.com/tasks/42",
"priority": "High",
"comment": "",
"claimed_by": "ada",
"due_date": "2026-09-20",
"sprint": "Sprint 1",
"tags": "release",
"mentions": "",
"member": "",
"fields": {"severity.level": "high"},
"actor_detail": {"id": 7, "name": "ada"},
"task_detail": {
"id": 42,
"title": "Ship",
"status": "Done",
"priority": "High"
},
"project_detail": {"id": 3, "name": "Ordryn"},
"changes": [{"field": "status", "old": "In progress", "new": "Done"}]
}Also present when applicable:
| Field | When |
|---|---|
count |
task.reordered, import.completed
|
join_email |
join.* |
config |
Generic select / status / user / string / int setting values |
digest_events |
Digest batches (up to 10 {name} ({event}) lines) |
callback_token, callback_url
|
Manifest declares any permissions
|
Email/due-dates relays read text as the email body. Use that same JSON shape.
Deliveries are queued: a short coalesce window (~3s), quiet hours, and hourly/daily digest still apply. Failed rows retry up to 8 attempts, then become dead. Owners can retry from Admin → Extensions, the project Extensions tab, or Settings → Integrations (POST …/deliveries/{id}/retry).
Discord (discord.webhook): messages include content plus an embed (title, status, actor, Open URL). When Discord returns a message id, later task.commented events post as thread replies (wait=true on create). Use Discord **bold** in templates.
Slack (slack.webhook): Block Kit with an Open button. Incoming webhooks cannot thread reliably without a Slack bot. Ordryn stores a provider message id when the webhook response includes ts and will thread later comments only in that case. Use Slack mrkdwn (*bold*).
Teams (teams.webhook): Adaptive Card with title and Open action. Use a Teams Workflow (“Post to a channel when a webhook request is received”). URLs often live on *.logic.azure.com or *.api.powerplatform.com and may include a query string.
Google Chat (googlechat.webhook): text plus cardsV2 (header, status/actor fields, Open button). Comments on the same task reuse a threadKey (ordryn-task-{id}) when Chat allows it. Use Chat text formatting (*bold*). Do not send this payload through http.webhook — Chat rejects extra JSON fields.
ntfy (ntfy.webhook): HTTPS topic URL such as https://ntfy.sh/my-topic. Optional setting ntfy_auth is sent as Authorization: Bearer …. Private/LAN ntfy hosts are rejected.
All types require HTTPS, no credentials in the URL, and no fragment. Loopback, private, link-local, and similar destinations are rejected (SSRF protection). Ordryn will not POST to localhost.
| Type | Extra host / path rules |
|---|---|
http.webhook |
Any public HTTPS host. Non-443 ports are allowed. |
discord.webhook |
discord.com, discordapp.com, canary.discord.com, or ptb.discord.com; path /api/webhooks/…; no query string. |
slack.webhook |
hooks.slack.com; path /services/…, /triggers/…, or /workflows/…. |
teams.webhook |
*.logic.azure.com, *.api.powerplatform.com, webhook.office.com, or Outlook Office hosts; path required. Query strings are allowed (Workflows URLs include them). |
googlechat.webhook |
Google Chat incoming webhook hosts (chat.googleapis.com URLs with key / token query params). |
ntfy.webhook |
Public HTTPS ntfy host. Private/LAN self-hosted ntfy is rejected. |
Outbound requests include X-Ordryn-Signature: sha256=<hex> when a signing secret is rotated on the Extensions panel (rotate_signing). The HMAC is over the raw JSON body.
Sample relays in email/relay, due-dates/relay, and callback-bot/relay verify that header against ORDRYN_SIGNING_SECRET. Copy the secret once after rotating it; the API does not return secret values later.
Inbound HMAC uses a different string (timestamp + "." + body). See Callbacks and inbound.