Repository navigation
Hooks
Hook extensions subscribe to events and POST JSON to a webhook URL configured on the site, project, or member destination. Declare each event as { "on": "<event>", "label": "…" }. Duplicate on values are rejected. The UI uses label; templates and APIs still use the event name.
Hook extensions also need delivery, at least one setting for url_from, and usually templates.
A project hook fires only when all of these are true:
- The extension is enabled for the site.
- The extension is enabled for the project (or the member destination is enabled).
- The event is listed in
hooks. - The destination included it in Triggers (
hook_select). Empty project/member trigger lists mean none. A trigger of*matches every declared hook. - A template exists for that event (destination override, else manifest default, else
templates["*"]on the destination). An empty template skips the send. - Declared filters also apply:
skip_self,claimed_only,claimed_is_me,min_priority,tag_ids,status_ids,status_exclude_ids, quiet hours,digest,field_filter,status_only. See Settings and controls.
join.* events are site-level. They use a site-scoped webhook URL (Admin → Extensions) and do not appear on project Extensions tabs. Empty site triggers for join hooks mean all declared join events.
The live UI (SSE) still refreshes on task.updated / project.updated / task.commented. Outbound hooks use the specific event.
These names are accepted by Ordryn (host_api 1). Example manifests may subscribe to a subset. webhook is a large set, not the complete catalog.
| Event | Typical meaning |
|---|---|
task.created |
A project task was created. |
task.updated |
Residual field edits (title, description, priority, parent, estimate, custom fields). Not double-fired with the specific hooks below. |
task.status_changed |
Kanban/list column changed. Subscribe here instead of relying on task.updated. |
task.deleted |
A project task was deleted. |
task.commented |
A comment was added. {comment} is truncated. Edit/delete/restore do not fire this outbound. |
task.comment_edited |
A comment was edited. |
task.comment_deleted |
A comment was deleted. |
task.comment_restored |
A deleted comment was restored. |
task.claimed |
Someone claimed the task. {claimed_by} is the claim holder. |
task.unclaimed |
The claim was cleared. |
task.due_changed |
Due date set, changed, or cleared. Empty {due_date} means cleared. |
task.due_soon |
Due tomorrow. Hourly worker; once per task per due date. |
task.overdue |
Due date passed. Hourly worker; once per task per due date. |
task.moved |
Task moved (including project or sprint). Still fires when the split hooks below also fire. |
task.project_changed |
Task moved to another project. Fires in addition to task.moved. |
task.sprint_changed |
Task sprint changed. Fires in addition to task.moved. |
task.tagged |
Tags changed. {tags} is the current tag list. |
task.mentioned |
Someone was @-mentioned in a comment. {mentions} is the named usernames. |
task.completed |
Task marked completed. |
task.reopened |
Task reopened. |
task.archived |
Task archived. |
task.restored |
Task restored from archive. |
task.reordered |
Board order changed. One project-level POST with {count}, not one per card. |
With Only notify when status changes (status_only) on, task.updated is skipped unless the kanban/list status actually changed. Prefer task.status_changed for column moves.
| Event | Typical meaning |
|---|---|
project.created |
A project was created. |
project.updated |
Project name, members, workflow, sprints, or GitHub link changed. |
project.deleted |
A project was deleted (not project.updated). |
project.archived |
A project was archived. |
project.restored |
A project was restored. |
project.member_joined |
A member joined. {member} is set. |
project.member_left |
A member left. {member} is set. |
project.member_role_changed |
A member’s role changed. |
project.invite_sent |
A project invite was sent. |
project.invite_declined |
A project invite was declined. |
sprint.created |
A sprint was created. {sprint} is the name. |
sprint.updated |
A sprint was edited. |
sprint.deleted |
A sprint was deleted. |
sprint.started |
Sprint start (worker + create/update paths; idempotent). |
sprint.ended |
Sprint end (worker + create/update paths; idempotent). |
import.completed |
CSV import finished. {count} is set. |
| Event | Typical meaning |
|---|---|
join.request |
Site join request. {join_email}, {join_message}. {url} is Admin → Join requests when PUBLIC_URL is set. |
join.approved |
Join request approved. |
join.denied |
Join request denied. |
Narrower examples:
-
comments —
task.commentedandtask.mentioned -
activity — board/audit events, skips routine
task.updated - lifecycle — split moves, archive/restore, membership, sprints
-
due-dates —
task.due_changed,task.due_soon,task.overdue - claimed — claim / unclaim / overdue
-
mentions —
task.mentionedonly -
join-requests —
join.request,join.approved,join.denied
join.* on the generic webhook example is site-level only. It is never posted into every project channel; prefer the dedicated join-requests extension for those events.
templates is a map of hook name → message string. Manifest keys must be known hook names.
Project/member owners can override them. If a destination sets a template to blank, that trigger is skipped. If the destination leaves a template unset, the manifest default is used. Destination overrides may set * as the fallback message when an event has no specific template.
Placeholders are case-insensitive. Unknown tokens become empty. Interpolated message text is trimmed to 2000 characters; @everyone / @here are stripped.
Markup is passed through unchanged — use Discord **bold**, Slack/Google Chat *bold*, or plain text depending on the destination.
| Token | Value |
|---|---|
{task}, {name}
|
Task title (max 120 characters; newlines flattened). Aliases of each other. |
{status} |
Current status name. |
{old_status} |
Previous status. |
{project} |
Project name. |
{project_id} |
Project id. |
{actor} |
Username or email of the person who caused the event. May be replaced by a chat mention when mention_map matches. |
{actor_id} |
Actor user id. |
{url} |
Task link from PUBLIC_URL (or a URL-like BASE_PATH) + /tasks/{id}. Project-level events use the project URL. For join.*, Admin → Join requests. |
{id} |
Task id. |
{priority} |
None, Low, Medium, or High. |
{old_priority} |
Previous priority when it changed. |
{description} |
Task description (truncated). |
{parent_id} |
Parent task id. |
{estimate} |
Estimate points. |
{comment} |
Comment body. Truncated. |
{claimed_by} |
Claim holder. |
{due_date} |
Due date; empty if cleared. |
{sprint} |
Sprint name when present. |
{tags} |
Current tags. |
{count} |
Count for task.reordered or import.completed. |
{mentions} |
Comma-separated @-mentioned usernames. |
{member} |
Member name on membership events. |
{event} |
Event name (for example task.created). |
{event_id} |
Event id. |
{occurred_at} |
RFC3339 timestamp. |
{changed} |
Comma-separated changed field keys. |
{fields}, {fields_json}
|
Custom fields JSON object. |
{changes_json} |
JSON array of { "field", "old", "new" }. |
{config_json} |
JSON object of generic setting values (select / status / user / string / int) when present. |
{join_email} |
Join-request email. |
{join_message} |
Join-request message. |
Set PUBLIC_URL in Ordryn’s .env (for example https://todo.example.com) so {url} is a clickable link.