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: |4 Create and manage ClickUp webhooks for real-time event notifications.5 Use when setting up webhook listeners for task/list/space events,6 implementing two-way sync, or handling ClickUp event payloads.7 Trigger: "clickup webhook", "clickup events", "clickup notifications",8 "clickup real-time", "clickup event listener", "clickup webhook create".9allowed-tools: Read, Write, Edit, Bash(curl:*)10version: 1.0.011license: MIT12author: Jeremy Longshore <jeremy@intentsolutions.io>13tags: [saas, productivity, clickup]14compatible-with: claude-code15---1617# ClickUp Webhooks & Events1819## Overview2021ClickUp 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.2223## Webhook Endpoints2425```26POST /api/v2/team/{team_id}/webhook Create webhook27GET /api/v2/team/{team_id}/webhook Get webhooks28PUT /api/v2/webhook/{webhook_id} Update webhook29DELETE /api/v2/webhook/{webhook_id} Delete webhook30```3132## Create a Webhook3334```typescript35async function createWebhook(teamId: string, endpoint: string, events: string[]) {36 return clickupRequest(`/team/${teamId}/webhook`, {37 method: 'POST',38 body: JSON.stringify({39 endpoint, // Your HTTPS URL40 events, // Array of event names41 space_id: null, // Optional: limit to specific space42 folder_id: null, // Optional: limit to specific folder43 list_id: null, // Optional: limit to specific list44 task_id: null, // Optional: limit to specific task45 }),46 });47}4849// Subscribe to task and list events50const webhook = await createWebhook('1234567', 'https://myapp.com/webhooks/clickup', [51 'taskCreated',52 'taskUpdated',53 'taskDeleted',54 'taskStatusUpdated',55 'taskAssigneeUpdated',56 'taskDueDateUpdated',57 'taskCommentPosted',58 'taskTimeTrackedUpdated',59 'listCreated',60 'listUpdated',61 'listDeleted',62]);6364// Response:65// { "id": "wh_abc123", "webhook": { "id": "...", "endpoint": "...", "events": [...] } }66```6768## Available Events6970| Category | Events |71|----------|--------|72| **Task** | `taskCreated`, `taskUpdated`, `taskDeleted`, `taskStatusUpdated`, `taskAssigneeUpdated`, `taskDueDateUpdated`, `taskTagUpdated`, `taskMoved`, `taskCommentPosted`, `taskCommentUpdated`, `taskTimeTrackedUpdated`, `taskTimeEstimateUpdated`, `taskPriorityUpdated` |73| **List** | `listCreated`, `listUpdated`, `listDeleted` |74| **Folder** | `folderCreated`, `folderUpdated`, `folderDeleted` |75| **Space** | `spaceCreated`, `spaceUpdated`, `spaceDeleted` |76| **Goal** | `goalCreated`, `goalUpdated`, `goalDeleted`, `keyResultCreated`, `keyResultUpdated`, `keyResultDeleted` |7778## Webhook Payload Format7980```json81{82 "event": "taskUpdated",83 "webhook_id": "wh_abc123",84 "task_id": "abc123",85 "history_items": [86 {87 "id": "hist_001",88 "type": 1,89 "date": "1695000000000",90 "field": "status",91 "parent_id": "abc123",92 "data": {},93 "source": null,94 "user": { "id": 183, "username": "john", "email": "john@example.com" },95 "before": { "status": "to do", "color": "#d3d3d3", "type": "open" },96 "after": { "status": "in progress", "color": "#4194f6", "type": "custom" }97 }98 ]99}100```101102## Webhook Handler (Express)103104```typescript105import express from 'express';106107const app = express();108app.use(express.json());109110app.post('/webhooks/clickup', async (req, res) => {111 const { event, webhook_id, task_id, history_items } = req.body;112113 // Immediately acknowledge (ClickUp expects 200 within 30s)114 res.status(200).json({ received: true });115116 // Process asynchronously117 try {118 await processClickUpEvent(event, task_id, history_items);119 } catch (err) {120 console.error(`Failed to process ${event} for task ${task_id}:`, err);121 }122});123124async function processClickUpEvent(125 event: string,126 taskId: string,127 historyItems: any[]128) {129 switch (event) {130 case 'taskCreated':131 console.log(`New task: ${taskId}`);132 break;133 case 'taskStatusUpdated': {134 const change = historyItems[0];135 console.log(`Task ${taskId}: ${change.before.status} -> ${change.after.status}`);136 // Trigger downstream actions (e.g., notify Slack, update external system)137 break;138 }139 case 'taskCommentPosted':140 console.log(`New comment on task ${taskId}`);141 break;142 case 'taskTimeTrackedUpdated':143 console.log(`Time tracked updated on task ${taskId}`);144 break;145 default:146 console.log(`Unhandled event: ${event}`);147 }148}149```150151## Idempotency (Prevent Duplicate Processing)152153```typescript154const processedEvents = new Map<string, number>();155156function isDuplicate(webhookId: string, historyItemId: string): boolean {157 const key = `${webhookId}:${historyItemId}`;158 if (processedEvents.has(key)) return true;159 processedEvents.set(key, Date.now());160161 // Clean old entries every 1000 events162 if (processedEvents.size > 10000) {163 const cutoff = Date.now() - 3600000; // 1 hour164 for (const [k, v] of processedEvents) {165 if (v < cutoff) processedEvents.delete(k);166 }167 }168 return false;169}170```171172## List and Manage Webhooks173174```bash175# List all webhooks for a workspace176TEAM_ID="1234567"177curl -s "https://api.clickup.com/api/v2/team/${TEAM_ID}/webhook" \178 -H "Authorization: $CLICKUP_API_TOKEN" | jq '.webhooks[] | {id, endpoint, events}'179180# Delete a webhook181curl -s -X DELETE "https://api.clickup.com/api/v2/webhook/WH_ID" \182 -H "Authorization: $CLICKUP_API_TOKEN"183```184185## Error Handling186187| Issue | Cause | Solution |188|-------|-------|----------|189| Webhook not firing | Endpoint not HTTPS | Webhooks require HTTPS URLs |190| Duplicate events | No idempotency | Track history_item IDs |191| Timeout (no 200) | Slow processing | Respond 200 immediately, process async |192| Webhook auto-disabled | Repeated failures | ClickUp disables after many 5xx responses |193194## Resources195196- [ClickUp Webhooks Guide](https://developer.clickup.com/docs/webhooks)197- [Task Webhook Payloads](https://developer.clickup.com/docs/webhooktaskpayloads)198- [List Webhook Payloads](https://developer.clickup.com/docs/webhooklistpayloads)199- [Create Webhook API](https://clickup.com/api/clickupreference/operation/CreateWebhook/)200201## Next Steps202203For performance optimization, see `clickup-performance-tuning`.204