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.
Source: jeremylongshore/claude-code-plugins-plus-skills → plugins/saas-packs/clickup-pack/skills/clickup-webhooks-events/SKILL.md
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". '4---56# ClickUp Webhooks & Events78## Overview910ClickUp 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.1112## Webhook Endpoints1314```15POST /api/v2/team/{team_id}/webhook Create webhook16GET /api/v2/team/{team_id}/webhook Get webhooks17PUT /api/v2/webhook/{webhook_id} Update webhook18DELETE /api/v2/webhook/{webhook_id} Delete webhook19```2021## Create a Webhook2223```typescript24async function createWebhook(teamId: string, endpoint: string, events: string[]) {25 return clickupRequest(`/team/${teamId}/webhook`, {26 method: 'POST',27 body: JSON.stringify({28 endpoint, // Your HTTPS URL29 events, // Array of event names30 space_id: null, // Optional: limit to specific space31 folder_id: null, // Optional: limit to specific folder32 list_id: null, // Optional: limit to specific list33 task_id: null, // Optional: limit to specific task34 }),35 });36}3738// Subscribe to task and list events39const webhook = await createWebhook('1234567', 'https://myapp.com/webhooks/clickup', [40 'taskCreated',41 'taskUpdated',42 'taskDeleted',43 'taskStatusUpdated',44 'taskAssigneeUpdated',45 'taskDueDateUpdated',46 'taskCommentPosted',47 'taskTimeTrackedUpdated',48 'listCreated',49 'listUpdated',50 'listDeleted',51]);5253// Response:54// { "id": "wh_abc123", "webhook": { "id": "...", "endpoint": "...", "events": [...] } }55```5657## Available Events5859| Category | Events |60|----------|--------|61| **Task** | `taskCreated`, `taskUpdated`, `taskDeleted`, `taskStatusUpdated`, `taskAssigneeUpdated`, `taskDueDateUpdated`, `taskTagUpdated`, `taskMoved`, `taskCommentPosted`, `taskCommentUpdated`, `taskTimeTrackedUpdated`, `taskTimeEstimateUpdated`, `taskPriorityUpdated` |62| **List** | `listCreated`, `listUpdated`, `listDeleted` |63| **Folder** | `folderCreated`, `folderUpdated`, `folderDeleted` |64| **Space** | `spaceCreated`, `spaceUpdated`, `spaceDeleted` |65| **Goal** | `goalCreated`, `goalUpdated`, `goalDeleted`, `keyResultCreated`, `keyResultUpdated`, `keyResultDeleted` |6667## Webhook Payload Format6869```json70{71 "event": "taskUpdated",72 "webhook_id": "wh_abc123",73 "task_id": "abc123",74 "history_items": [75 {76 "id": "hist_001",77 "type": 1,78 "date": "1695000000000",79 "field": "status",80 "parent_id": "abc123",81 "data": {},82 "source": null,83 "user": { "id": 183, "username": "john", "email": "john@example.com" },84 "before": { "status": "to do", "color": "#d3d3d3", "type": "open" },85 "after": { "status": "in progress", "color": "#4194f6", "type": "custom" }86 }87 ]88}89```9091## Webhook Handler (Express)9293```typescript94import express from 'express';9596const app = express();97app.use(express.json());9899app.post('/webhooks/clickup', async (req, res) => {100 const { event, webhook_id, task_id, history_items } = req.body;101102 // Immediately acknowledge (ClickUp expects 200 within 30s)103 res.status(200).json({ received: true });104105 // Process asynchronously106 try {107 await processClickUpEvent(event, task_id, history_items);108 } catch (err) {109 console.error(`Failed to process ${event} for task ${task_id}:`, err);110 }111});112113async function processClickUpEvent(114 event: string,115 taskId: string,116 historyItems: any[]117) {118 switch (event) {119 case 'taskCreated':120 console.log(`New task: ${taskId}`);121 break;122 case 'taskStatusUpdated': {123 const change = historyItems[0];124 console.log(`Task ${taskId}: ${change.before.status} -> ${change.after.status}`);125 // Trigger downstream actions (e.g., notify Slack, update external system)126 break;127 }128 case 'taskCommentPosted':129 console.log(`New comment on task ${taskId}`);130 break;131 case 'taskTimeTrackedUpdated':132 console.log(`Time tracked updated on task ${taskId}`);133 break;134 default:135 console.log(`Unhandled event: ${event}`);136 }137}138```139140## Idempotency (Prevent Duplicate Processing)141142```typescript143const processedEvents = new Map<string, number>();144145function isDuplicate(webhookId: string, historyItemId: string): boolean {146 const key = `${webhookId}:${historyItemId}`;147 if (processedEvents.has(key)) return true;148 processedEvents.set(key, Date.now());149150 // Clean old entries every 1000 events151 if (processedEvents.size > 10000) {152 const cutoff = Date.now() - 3600000; // 1 hour153 for (const [k, v] of processedEvents) {154 if (v < cutoff) processedEvents.delete(k);155 }156 }157 return false;158}159```160161## List and Manage Webhooks162163```bash164# List all webhooks for a workspace165TEAM_ID="1234567"166curl -s "https://api.clickup.com/api/v2/team/${TEAM_ID}/webhook" \167 -H "Authorization: $CLICKUP_API_TOKEN" | jq '.webhooks[] | {id, endpoint, events}'168169# Delete a webhook170curl -s -X DELETE "https://api.clickup.com/api/v2/webhook/WH_ID" \171 -H "Authorization: $CLICKUP_API_TOKEN"172```173174## Error Handling175176| Issue | Cause | Solution |177|-------|-------|----------|178| Webhook not firing | Endpoint not HTTPS | Webhooks require HTTPS URLs |179| Duplicate events | No idempotency | Track history_item IDs |180| Timeout (no 200) | Slow processing | Respond 200 immediately, process async |181| Webhook auto-disabled | Repeated failures | ClickUp disables after many 5xx responses |182183## Resources184185- [ClickUp Webhooks Guide](https://developer.clickup.com/docs/webhooks)186- [Task Webhook Payloads](https://developer.clickup.com/docs/webhooktaskpayloads)187- [List Webhook Payloads](https://developer.clickup.com/docs/webhooklistpayloads)188- [Create Webhook API](https://clickup.com/api/clickupreference/operation/CreateWebhook/)189190## Next Steps191192For performance optimization, see `clickup-performance-tuning`.193194---195196**Source:** [`jeremylongshore/claude-code-plugins-plus-skills`](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) → `plugins/saas-packs/clickup-pack/skills/clickup-webhooks-events/SKILL.md`