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.