ClickUp Webhooks & Events
Overview
ClickUp webhooks send HTTP POST notifications when resources change. Register webhooks via API, subscribe to specific events, and receive payloads with history_items showing what changed.
Webhook Endpoints
POST /api/v2/team/{team_id}/webhook Create webhook
GET /api/v2/team/{team_id}/webhook Get webhooks
PUT /api/v2/webhook/{webhook_id} Update webhook
DELETE /api/v2/webhook/{webhook_id} Delete webhook
Create a Webhook
async function createWebhook(teamId: string, endpoint: string, events: string[]) {
return clickupRequest(`/team/${teamId}/webhook`, {
method: 'POST',
body: JSON.stringify({
endpoint, // Your HTTPS URL
events, // Array of event names
space_id: null, // Optional: limit to specific space
folder_id: null, // Optional: limit to specific folder
list_id: null, // Optional: limit to specific list
task_id: null, // Optional: limit to specific task
}),
});
}
// Subscribe to task and list events
const webhook = await createWebhook('1234567', 'https://myapp.com/webhooks/clickup', [
'taskCreated',
'taskUpdated',
'taskDeleted',
'taskStatusUpdated',
'taskAssigneeUpdated',
'taskDueDateUpdated',
'taskCommentPosted',
'taskTimeTrackedUpdated',
'listCreated',
'listUpdated',
'listDeleted',
]);
// Response:
// { "id": "wh_abc123", "webhook": { "id": "...", "endpoint": "...", "events": [...] } }
Available Events
| Category |
Events |
| Task |
taskCreated, taskUpdated, taskDeleted, taskStatusUpdated, taskAssigneeUpdated, taskDueDateUpdated, taskTagUpdated, taskMoved, taskCommentPosted, taskCommentUpdated, taskTimeTrackedUpdated, taskTimeEstimateUpdated, taskPriorityUpdated |
| List |
listCreated, listUpdated, listDeleted |
| Folder |
folderCreated, folderUpdated, folderDeleted |
| Space |
spaceCreated, spaceUpdated, spaceDeleted |
| Goal |
goalCreated, goalUpdated, goalDeleted, keyResultCreated, keyResultUpdated, keyResultDeleted |
Webhook Payload Format
{
"event": "taskUpdated",
"webhook_id": "wh_abc123",
"task_id": "abc123",
"history_items": [
{
"id": "hist_001",
"type": 1,
"date": "1695000000000",
"field": "status",
"parent_id": "abc123",
"data": {},
"source": null,
"user": { "id": 183, "username": "john", "email": "john@example.com" },
"before": { "status": "to do", "color": "#d3d3d3", "type": "open" },
"after": { "status": "in progress", "color": "#4194f6", "type": "custom" }
}
]
}
Webhook Handler (Express)
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/clickup', async (req, res) => {
const { event, webhook_id, task_id, history_items } = req.body;
// Immediately acknowledge (ClickUp expects 200 within 30s)
res.status(200).json({ received: true });
// Process asynchronously
try {
await processClickUpEvent(event, task_id, history_items);
} catch (err) {
console.error(`Failed to process ${event} for task ${task_id}:`, err);
}
});
async function processClickUpEvent(
event: string,
taskId: string,
historyItems: any[]
) {
switch (event) {
case 'taskCreated':
console.log(`New task: ${taskId}`);
break;
case 'taskStatusUpdated': {
const change = historyItems[0];
console.log(`Task ${taskId}: ${change.before.status} -> ${change.after.status}`);
// Trigger downstream actions (e.g., notify Slack, update external system)
break;
}
case 'taskCommentPosted':
console.log(`New comment on task ${taskId}`);
break;
case 'taskTimeTrackedUpdated':
console.log(`Time tracked updated on task ${taskId}`);
break;
default:
console.log(`Unhandled event: ${event}`);
}
}
Idempotency (Prevent Duplicate Processing)
const processedEvents = new Map<string, number>();
function isDuplicate(webhookId: string, historyItemId: string): boolean {
const key = `${webhookId}:${historyItemId}`;
if (processedEvents.has(key)) return true;
processedEvents.set(key, Date.now());
// Clean old entries every 1000 events
if (processedEvents.size > 10000) {
const cutoff = Date.now() - 3600000; // 1 hour
for (const [k, v] of processedEvents) {
if (v < cutoff) processedEvents.delete(k);
}
}
return false;
}
List and Manage Webhooks
# List all webhooks for a workspace
TEAM_ID="1234567"
curl -s "https://api.clickup.com/api/v2/team/${TEAM_ID}/webhook" \
-H "Authorization: $CLICKUP_API_TOKEN" | jq '.webhooks[] | {id, endpoint, events}'
# Delete a webhook
curl -s -X DELETE "https://api.clickup.com/api/v2/webhook/WH_ID" \
-H "Authorization: $CLICKUP_API_TOKEN"
Error Handling
| Issue |
Cause |
Solution |
| Webhook not firing |
Endpoint not HTTPS |
Webhooks require HTTPS URLs |
| Duplicate events |
No idempotency |
Track history_item IDs |
| Timeout (no 200) |
Slow processing |
Respond 200 immediately, process async |
| Webhook auto-disabled |
Repeated failures |
ClickUp disables after many 5xx responses |
Resources
Next Steps
For performance optimization, see clickup-performance-tuning.
1---2name: clickup-webhooks-events3description: Create and manage ClickUp webhooks for real-time event notifications. Use when setting up webhook listeners for task/list/space events, implementing two-way sync, or handling ClickUp event payloads. Trigger: "clickup webhook", "clickup events", "clickup notifications", "clickup real-time", "clickup event listener", "clickup webhook create".4license: MIT5---67# ClickUp Webhooks & Events89## Overview1011ClickUp webhooks send HTTP POST notifications when resources change. Register webhooks via API, subscribe to specific events, and receive payloads with `history_items` showing what changed.1213## Webhook Endpoints1415```16POST /api/v2/team/{team_id}/webhook Create webhook17GET /api/v2/team/{team_id}/webhook Get webhooks18PUT /api/v2/webhook/{webhook_id} Update webhook19DELETE /api/v2/webhook/{webhook_id} Delete webhook20```2122## Create a Webhook2324```typescript25async function createWebhook(teamId: string, endpoint: string, events: string[]) {26 return clickupRequest(`/team/${teamId}/webhook`, {27 method: 'POST',28 body: JSON.stringify({29 endpoint, // Your HTTPS URL30 events, // Array of event names31 space_id: null, // Optional: limit to specific space32 folder_id: null, // Optional: limit to specific folder33 list_id: null, // Optional: limit to specific list34 task_id: null, // Optional: limit to specific task35 }),36 });37}3839// Subscribe to task and list events40const webhook = await createWebhook('1234567', 'https://myapp.com/webhooks/clickup', [41 'taskCreated',42 'taskUpdated',43 'taskDeleted',44 'taskStatusUpdated',45 'taskAssigneeUpdated',46 'taskDueDateUpdated',47 'taskCommentPosted',48 'taskTimeTrackedUpdated',49 'listCreated',50 'listUpdated',51 'listDeleted',52]);5354// Response:55// { "id": "wh_abc123", "webhook": { "id": "...", "endpoint": "...", "events": [...] } }56```5758## Available Events5960| Category | Events |61|----------|--------|62| **Task** | `taskCreated`, `taskUpdated`, `taskDeleted`, `taskStatusUpdated`, `taskAssigneeUpdated`, `taskDueDateUpdated`, `taskTagUpdated`, `taskMoved`, `taskCommentPosted`, `taskCommentUpdated`, `taskTimeTrackedUpdated`, `taskTimeEstimateUpdated`, `taskPriorityUpdated` |63| **List** | `listCreated`, `listUpdated`, `listDeleted` |64| **Folder** | `folderCreated`, `folderUpdated`, `folderDeleted` |65| **Space** | `spaceCreated`, `spaceUpdated`, `spaceDeleted` |66| **Goal** | `goalCreated`, `goalUpdated`, `goalDeleted`, `keyResultCreated`, `keyResultUpdated`, `keyResultDeleted` |6768## Webhook Payload Format6970```json71{72 "event": "taskUpdated",73 "webhook_id": "wh_abc123",74 "task_id": "abc123",75 "history_items": [76 {77 "id": "hist_001",78 "type": 1,79 "date": "1695000000000",80 "field": "status",81 "parent_id": "abc123",82 "data": {},83 "source": null,84 "user": { "id": 183, "username": "john", "email": "john@example.com" },85 "before": { "status": "to do", "color": "#d3d3d3", "type": "open" },86 "after": { "status": "in progress", "color": "#4194f6", "type": "custom" }87 }88 ]89}90```9192## Webhook Handler (Express)9394```typescript95import express from 'express';9697const app = express();98app.use(express.json());99100app.post('/webhooks/clickup', async (req, res) => {101 const { event, webhook_id, task_id, history_items } = req.body;102103 // Immediately acknowledge (ClickUp expects 200 within 30s)104 res.status(200).json({ received: true });105106 // Process asynchronously107 try {108 await processClickUpEvent(event, task_id, history_items);109 } catch (err) {110 console.error(`Failed to process ${event} for task ${task_id}:`, err);111 }112});113114async function processClickUpEvent(115 event: string,116 taskId: string,117 historyItems: any[]118) {119 switch (event) {120 case 'taskCreated':121 console.log(`New task: ${taskId}`);122 break;123 case 'taskStatusUpdated': {124 const change = historyItems[0];125 console.log(`Task ${taskId}: ${change.before.status} -> ${change.after.status}`);126 // Trigger downstream actions (e.g., notify Slack, update external system)127 break;128 }129 case 'taskCommentPosted':130 console.log(`New comment on task ${taskId}`);131 break;132 case 'taskTimeTrackedUpdated':133 console.log(`Time tracked updated on task ${taskId}`);134 break;135 default:136 console.log(`Unhandled event: ${event}`);137 }138}139```140141## Idempotency (Prevent Duplicate Processing)142143```typescript144const processedEvents = new Map<string, number>();145146function isDuplicate(webhookId: string, historyItemId: string): boolean {147 const key = `${webhookId}:${historyItemId}`;148 if (processedEvents.has(key)) return true;149 processedEvents.set(key, Date.now());150151 // Clean old entries every 1000 events152 if (processedEvents.size > 10000) {153 const cutoff = Date.now() - 3600000; // 1 hour154 for (const [k, v] of processedEvents) {155 if (v < cutoff) processedEvents.delete(k);156 }157 }158 return false;159}160```161162## List and Manage Webhooks163164```bash165# List all webhooks for a workspace166TEAM_ID="1234567"167curl -s "https://api.clickup.com/api/v2/team/${TEAM_ID}/webhook" \168 -H "Authorization: $CLICKUP_API_TOKEN" | jq '.webhooks[] | {id, endpoint, events}'169170# Delete a webhook171curl -s -X DELETE "https://api.clickup.com/api/v2/webhook/WH_ID" \172 -H "Authorization: $CLICKUP_API_TOKEN"173```174175## Error Handling176177| Issue | Cause | Solution |178|-------|-------|----------|179| Webhook not firing | Endpoint not HTTPS | Webhooks require HTTPS URLs |180| Duplicate events | No idempotency | Track history_item IDs |181| Timeout (no 200) | Slow processing | Respond 200 immediately, process async |182| Webhook auto-disabled | Repeated failures | ClickUp disables after many 5xx responses |183184## Resources185186- [ClickUp Webhooks Guide](https://developer.clickup.com/docs/webhooks)187- [Task Webhook Payloads](https://developer.clickup.com/docs/webhooktaskpayloads)188- [List Webhook Payloads](https://developer.clickup.com/docs/webhooklistpayloads)189- [Create Webhook API](https://clickup.com/api/clickupreference/operation/CreateWebhook/)190191## Next Steps192193For performance optimization, see `clickup-performance-tuning`.194