Skillshub clickup-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/clickup-webhooks-events" ~/.claude/skills/comeonoliver-skillshub-clickup-webhooks-events && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/clickup-webhooks-events/SKILL.mdsource content
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 | , , , , , , , , , , , , |
| List | , , |
| Folder | , , |
| Space | , , |
| Goal | , , , , , |
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.