Webhooks
Webhooks let you receive HTTP POST requests when events happen in your projects. Use them to integrate TaskView with external systems - CI/CD pipelines, Slack bots, custom dashboards, or any service that can accept HTTP requests.
Supported events
| Event | When it fires |
|---|---|
task.created | A new task is created in the project |
task.updated | A task is updated (description, status, priority, deadline, etc.) |
task.deleted | A task is deleted |
task.moved | A task is moved to another project. Sent to the webhooks of both projects |
task.assigneesChanged | Task assignees are added or removed |
time-entry.started | A timer is started on a task |
time-entry.stopped | A running timer is stopped |
time-entry.created | A time entry is created manually |
time-entry.updated | A time entry is updated |
time-entry.deleted | A time entry is deleted |
sprint.created | A new sprint is created (draft or planned) |
sprint.updated | Sprint fields are edited (name, dates, capacity, goal) |
sprint.activated | A sprint transitions to the active state |
sprint.reviewStarted | An active sprint enters review |
sprint.completed | A sprint is closed and marked completed |
sprint.paused | An active sprint is paused |
sprint.resumed | A paused sprint is resumed |
sprint.deleted | A sprint is deleted (any status, including completed) |
task.assignedToSprint | A task is added to, moved between, or removed from a sprint |
file.uploaded | A file is uploaded to the project or straight into a task |
file.renamed | A file is renamed |
file.deleted | A file is deleted forever |
file.attached | An existing project file is attached to a task |
file.detached | A file is removed from a task (the file itself stays in the project) |
Setup
- Open a project in TaskView
- Right-click the project in the sidebar → "Webhooks"
- Click "Add Webhook"
- Enter the URL where you want to receive events
- Select which events to subscribe to
- Click "Add"
- Copy the secret and store it securely - it will not be shown again
Permissions
Two project permissions, in the Webhooks group of the role editor, decide who can work with webhooks:
| Permission | Allows |
|---|---|
webhooks_can_view | See the project's webhooks with their URLs and events, and the delivery history including payloads |
webhooks_can_manage | Create, edit, enable/disable, delete and test webhooks, rotate their secrets and retry deliveries. It does not include viewing: without webhooks_can_view the webhooks list and delivery history stay hidden |
The two permissions are independent: to see and change webhooks a member needs both. The project owner has both. Any other member gets them only through a role: neither permission is part of the default roles, so add them to a role in the role editor. A webhook sends every subscribed event of the project to its URL, so grant webhooks_can_manage only to people you trust with all project data, and webhooks_can_view only to people who may see the URLs - many receivers treat the URL itself as a secret.
API tokens and OAuth apps are narrowed as usual: a token restricted to some projects or permissions can only reach the webhooks those allow.
Allowed destinations
A webhook URL must use http or https and point to a public address. URLs that resolve to loopback (127.0.0.1, localhost), private (10/8, 172.16/12, 192.168/16), link-local or cloud-metadata ranges are rejected when you save the webhook, and checked again before every delivery. Redirects are not followed: if your endpoint answers with a 3xx, the delivery is recorded as failed with that status code.
On a self-hosted instance where your receivers (n8n, CI runners, internal bots) live on the same private network, set WEBHOOKS_ALLOW_PRIVATE_URLS=true in the API environment. See environment variables for details.
Payload format
Every webhook delivery is an HTTP POST with Content-Type: application/json:
{
"event": "task.updated",
"timestamp": "2026-03-22T12:00:00.000Z",
"task": {
"id": 123,
"goalId": 774,
"description": "Fix login bug",
"complete": false,
"statusId": 5,
"priorityId": 2,
"tags": [1, 3],
"assignedUsers": [10, 22],
"subtasks": []
},
"changes": {
"statusId": 5
},
"initiatorId": 1
}
The changes field is only present on task.updated and sprint.updated events and contains only the fields that changed.
Sprint events carry the sprint payload. Lifecycle events (sprint.activated, sprint.reviewStarted, sprint.completed, sprint.paused, sprint.resumed) include the full sprint object reflecting its state at the time the event fired, plus sprintId, goalId, and initiatorId:
{
"event": "sprint.completed",
"timestamp": "2026-03-22T12:00:00.000Z",
"sprintId": 42,
"goalId": 774,
"initiatorId": 1,
"sprint": {
"id": 42,
"goalId": 774,
"name": "Sprint 7",
"status": "completed",
"startDate": "2026-03-08",
"endDate": "2026-03-22",
"goalAchieved": true,
"completedAt": "2026-03-22T12:00:00.000Z"
}
}
task.assignedToSprint carries taskId, sprintId (the new sprint, or null when removed), prevSprintId, goalId, and initiatorId. sprint.deleted carries sprintId, goalId, and initiatorId only, since the sprint no longer exists. initiatorId may be null for sprints generated automatically by a cadence.
task.moved carries task (the task after the move), taskIds (the moved task and its subtasks), fromGoalId, toGoalId and initiatorId. When the user may not delete tasks in the source project, the task is copied instead and the target project receives task.created for every copied task.
File events share one payload shape:
{
"event": "file.attached",
"timestamp": "2026-10-05T12:00:00.000Z",
"goalId": 1,
"taskIds": [42],
"initiatorId": 7,
"file": {
"id": "6f1c2a8e-3b1d-4c9e-9a0b-2d5e7f8a1b3c",
"goalId": 1,
"name": "report.pdf",
"originalName": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 248512,
"uploaderId": 7,
"uploaderEmail": "[email protected]",
"createdAt": "2026-10-05T11:58:00.000Z",
"linkedTaskIds": [17, 42]
}
}
taskIds lists the tasks the event concerns: every task the file is attached to for file.uploaded, file.renamed and file.deleted, the single task for file.attached and file.detached. file.linkedTaskIds is the state after the change; for file.deleted it is the state just before deletion. Payloads carry metadata only - no download link, so the file content never leaves TaskView through a webhook. Fetch the content through the API with a token that has the file_can_view permission.
Signature verification
Every request includes an X-Webhook-Signature header with an HMAC-SHA256 signature of the request body:
X-Webhook-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
Always verify the signature before processing the payload. Example in Node.js:
const crypto = require('crypto')
function verifySignature(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex')
return signature === expected
}
// In your HTTP handler:
const body = req.body // raw string, not parsed JSON
const signature = req.headers['x-webhook-signature']
const isValid = verifySignature(body, signature, YOUR_SECRET)
Retries
If your server responds with a non-2xx status code or doesn't respond within 10 seconds, TaskView retries the delivery:
| Attempt | Delay |
|---|---|
| 1st retry | ~10 seconds |
| 2nd retry | ~20 seconds |
After 3 total attempts (1 original + 2 retries), the delivery is marked as failed.
Auto-deactivation
If a webhook accumulates 10 consecutive failed deliveries (after all retries are exhausted), it is automatically deactivated. A single successful delivery resets the failure counter.
To reactivate a webhook, toggle it back on from the webhooks page. The failure counter is not reset automatically - the next successful delivery will reset it.
Delivery history
The webhooks page shows delivery history for each webhook:
- Event - which event was delivered
- Status - success, failed, or pending
- HTTP code - response status code from your server
- Attempts - how many attempts were made
- Payload - click to view the full JSON payload
Failed deliveries can be retried manually from the delivery history.
Managing webhooks
From the webhooks page you can:
- Toggle webhooks on/off
- Edit the URL and subscribed events
- Test - sends a test payload to verify connectivity
- Rotate secret - generates a new secret (the old one stops working immediately)
- Delete - removes the webhook and all delivery history
- View deliveries - see delivery history with status filter
Secret rotation
If your secret is compromised, rotate it:
- Click the key icon on the webhook
- Confirm that you want to rotate
- Copy the new secret
- Update the secret in your receiving application
The old secret stops working immediately. Any in-flight deliveries signed with the old secret will fail signature verification on your end.
Testing locally
You can use a simple Node.js script to test webhook deliveries:
const http = require('http')
const crypto = require('crypto')
const PORT = 4545
const SECRET = 'your-secret-here'
const server = http.createServer((req, res) => {
const chunks = []
req.on('data', (chunk) => chunks.push(chunk))
req.on('end', () => {
const body = Buffer.concat(chunks).toString()
const signature = req.headers['x-webhook-signature'] || ''
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(body)
.digest('hex')
console.log(signature === expected ? 'Valid' : 'INVALID')
console.log(JSON.stringify(JSON.parse(body), null, 2))
res.writeHead(200)
res.end('OK')
})
})
server.listen(PORT, () => console.log(`Listening on :${PORT}`))
Run with node webhook-receiver.js and set the webhook URL to http://localhost:4545. Loopback addresses are refused by default, so for this local test start the API with WEBHOOKS_ALLOW_PRIVATE_URLS=true, or expose the receiver through a tunnel (ngrok, Cloudflare Tunnel) and use its public URL.
Notifications
Real-time and push notifications in TaskView - deadline alerts, assignment notifications, per-user preferences, and multi-channel delivery via WebSocket and Firebase Cloud Messaging.
API Tokens
Create API tokens for programmatic access to TaskView. Tokens support permission scoping, project-level restrictions, and optional expiration.
