ClickUp webhooks: create them with the API, verify X-Signature, avoid suspension

To create a ClickUp webhook, POST an endpoint URL and a list of events to /api/v2/team/{team_id}/webhook with your API token. Point it at a free webhooks.sh bin to see every event and its X-Signature header live: no signup, 48 hours, 100 requests a day.

ClickUp webhooks are registered through the ClickUp webhooks API with a single POST carrying your endpoint and event list, and if that endpoint keeps failing, ClickUp suspends the webhook without warning. The quickest way to inspect real payloads and the X-Signature header is to point a new webhook at a free bin and edit a task. Below, we also cover testing the Call webhook automation action.

Register a webhook against a free bin

  1. Open view.webhooks.page in your browser to create a bin, or run curl -X POST https://webhooks.sh/v1/bins to get an ingest_url and view_url in JSON.
  2. Send a POST request to /api/v2/team/{team_id}/webhook with your personal API token (pk_...) in the Authorization header, your bin's ingest_url as the endpoint, a short events list, and a list_id so you only get events from one list while testing.
  3. Grab the secret field from the JSON response and save it, because ClickUp uses it to sign every delivery for that webhook.
  4. Change a task inside your test list and watch the request show up live in your bin viewer.

The bin replies to every request with HTTP 200, which keeps your webhook in an active state.

# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# 2. Register the bin as a ClickUp webhook for one list
curl -X POST "https://api.clickup.com/api/v2/team/<team_id>/webhook" \
  -H "Authorization: pk_<your_token>" \
  -H "Content-Type: application/json" \
  -d '{"endpoint": "https://<bin>.webhooks.page/clickup", "events": ["taskCreated", "taskStatusUpdated"], "list_id": 4567}'

Events and location filters

ClickUp has 28 webhook events across tasks, lists, folders, spaces, and goals. Passing * in the events array subscribes your endpoint to all of them, and you can narrow the scope by passing one ID per level using space_id, folder_id, list_id, or task_id (if you pass more than one, the most specific level wins).

When you're testing against a free bin, stick to a single list_id and a few specific events like taskUpdated or taskStatusUpdated. Putting * on an active workspace will burn through the free bin's 100 requests a day fast, and once you hit the cap the bin returns 429 (daily_cap_reached), which ClickUp treats as a delivery failure.

ResourceCountEvents
Task13taskCreated, taskUpdated, taskDeleted, taskPriorityUpdated, taskStatusUpdated, taskAssigneeUpdated, taskDueDateUpdated, taskTagUpdated, taskMoved, taskCommentPosted, taskCommentUpdated, taskTimeEstimateUpdated, taskTimeTrackedUpdated
List3listCreated, listUpdated, listDeleted
Folder3folderCreated, folderUpdated, folderDeleted
Space3spaceCreated, spaceUpdated, spaceDeleted
Goal6goalCreated, goalUpdated, goalDeleted, keyResultCreated, keyResultUpdated, keyResultDeleted

What a ClickUp event looks like

Every delivery is an HTTP POST with Content-Type set to application/json. The JSON body always includes webhook_id, event, and the resource ID such as task_id, along with a history_items array holding before and after values whenever ClickUp provides them.

When you create a new task, ClickUp sends both taskCreated and taskStatusUpdated, so with both subscribed you'll see two requests for one new task. ClickUp sends webhooks from dynamic IP addresses rather than a fixed range, so verify the signature header instead of allowlisting IPs.

{
  "event": "taskStatusUpdated",
  "history_items": [
    {
      "id": "2800763136717140857",
      "type": 1,
      "date": "1642734631523",
      "field": "status",
      "parent_id": "162641062",
      "user": { "id": 183, "username": "John" },
      "before": { "status": null, "type": "removed" },
      "after": { "status": "to do", "type": "open" }
    }
  ],
  "task_id": "1vj37mc",
  "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204"
}

Verify the X-Signature header

When you create a webhook, the API response includes a unique secret string for that registration. ClickUp hashes the raw request body with HMAC-SHA256 using that secret and puts the hex digest in the X-Signature header.

You need to hash the raw body bytes before your server modifies them, though if your framework already parsed the JSON, ClickUp notes you can stringify the object with no added whitespace before hashing. Because X-Signature isn't redacted in the webhooks.sh viewer, you can copy a captured body and header straight from your bin and test them with your secret in the HMAC signature verifier before writing any code.

import { createHmac, timingSafeEqual } from "node:crypto";

export function isFromClickUp(rawBody, signatureHeader, webhookSecret) {
  const expected = createHmac("sha256", webhookSecret).update(rawBody).digest("hex");
  const got = Buffer.from(signatureHeader ?? "");
  const want = Buffer.from(expected);
  return got.length === want.length && timingSafeEqual(got, want);
}

Health status, fail_count and suspension

ClickUp tracks delivery health on every webhook and silently suspends endpoints that keep failing.

You can check health.status and fail_count on the webhook object through the API, and if a webhook gets suspended, reactivate it by sending PUT /api/v2/webhook/{webhook_id} with status set to active along with your endpoint and events. Make sure your handler never returns 401 or 410 by mistake, because either code suspends the webhook on the spot instead of incrementing fail_count.

When you finish testing, use the same PUT to point the webhook at your real server, or delete it. Free bins stop accepting traffic after 48 hours, so leaving a webhook pointed at an expired bin will run up its fail_count (see our webhook not received guide if deliveries stop showing up).

ConditionWhat ClickUp does
Unsuccessful status or slower than 7 secondsMarks the webhook Failing and retries the event up to 5 times
5 failed attempts for one eventAdds 1 to fail_count and stops sending that event
fail_count reaches 100Suspends the webhook
Response is 401 or 410Suspends the webhook immediately
Endpoint recoversBack to active, fail_count resets, failed events are not resent

Test the Call webhook automation action

You can also send webhooks from ClickUp Automations by setting up a task trigger in a Space, Folder, Subfolder, or List and selecting the Call webhook action (availability depends on your ClickUp plan). The JSON body for an automation webhook doesn't match the API webhook format: it sends auto_id, trigger_id, date, and a payload object containing the task fields.

Clicking the Test button in the Automations Webhooks tab only tells you whether the request succeeded or failed, so if you want to inspect the actual JSON structure, paste a free bin URL into the action and trigger the task rule once. Keep two things in mind when testing automations: if you attach a custom header whose name includes authorization, cookie, key, token, or secret, its value shows as redacted in the bin viewer, and if an active automation call fails, ClickUp retries it for 1 hour and 15 minutes while showing it as pending in the Activity tab.

Keep the URL or relay to localhost

A free bin runs for 48 hours and accepts up to 100 requests per day. To keep your registered webhook working without swapping the URL, claim the bin with a free Google sign-in before it expires; the timer stops and you keep 30 days of rolling history at the same address.

For more volume, Solo ($12 a month, see pricing) gives you persistent named endpoints on webhooks.host with 250,000 requests per billing period. Solo includes the wsh CLI so you can relay live ClickUp deliveries straight to your local machine with wsh listen <endpoint> --to localhost:3000, plus automatic forwarding to up to 3 HTTPS URLs.

FAQ

How do I create a webhook in ClickUp?

Send a POST request with your endpoint URL and an events array to /api/v2/team/{team_id}/webhook, authenticated with your API token in the Authorization header. The response includes the secret that signs its payloads.

Why was my ClickUp webhook suspended?

ClickUp suspends a webhook when its fail_count hits 100, or immediately if your server responds with HTTP 401 or 410. Every time an event fails all 5 delivery attempts, ClickUp adds 1 to fail_count.

How do I reactivate a suspended ClickUp webhook?

Send a PUT request to /api/v2/webhook/{webhook_id} with status set to active, along with the endpoint and events fields.

How do I verify ClickUp webhook signatures?

Compute a hex HMAC-SHA256 digest of the raw request body using the secret returned when you created the webhook, then compare your result against the request's X-Signature header.

Does ClickUp retry failed webhooks?

Yes, ClickUp retries each failed API webhook event up to 5 times. Any response that returns an error status or takes longer than 7 seconds counts as a failure, and once an event fails all 5 attempts it won't be sent again.

What IP addresses do ClickUp webhooks come from?

None are fixed. ClickUp sends from dynamic addresses, so check the X-Signature header instead of filtering by IP.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.