Skip to content

fix: webhook update sends a body ClickUp accepts - #8

Merged
pikonha merged 1 commit into
mainfrom
fix/webhook-update-payload
Aug 20, 2026
Merged

fix: webhook update sends a body ClickUp accepts#8
pikonha merged 1 commit into
mainfrom
fix/webhook-update-payload

Conversation

@pikonha

@pikonha pikonha commented Aug 20, 2026

Copy link
Copy Markdown
Member

webhook update could not repoint a webhook's endpoint. Found while moving a live subscription between two agents; the workaround was a raw curl PUT.

Three bugs, all in the request body

1. Events was typed string. ClickUp wants an array:

body result
{"events":"taskStatusUpdated"} 400 {"err":"Invalid events","ECODE":"OAUTH_150"}
{"events":["taskStatusUpdated"]} 200

webhook list returns an array, so round-tripping a value the API just gave you failed. create already had this right (StringSlice); only update did not.

2. No omitempty. An endpoint-only update sent "status": "" and "events": "" along with it:

body result
{"endpoint":"…"} 200
{"endpoint":"…","status":""} 500 {"err":"Internal Server Error","ECODE":"OAUTH_152"}

The endpoint-only update the CLI could not do is one the API accepts happily. PUT /v2/webhook/{id} behaves like PATCH — it preserves every field the body omits — so omitempty across the struct is the whole fix.

3. --events '*' silently unsubscribes. The flag help said "use * for all". ClickUp answers 200 and sets events: [] — subscribing to nothing:

{"events":["*"]}  → 200, events = []
{"events":"*"}    → 200, events = []

That is a live webhook going quiet with no error to notice. Now refused with a message naming why; the help text no longer recommends it.

Plus: an update with no fields at all is refused instead of sending an empty body.

Verification

TestUpdateWebhookBody asserts the bytes on the wire (endpoint-only omits the other two; events marshal as an array; status alone), because these are payload bugs — asserting on the response would have passed throughout.

Against the live API with a throwaway webhook, since a fake server cannot reproduce OAUTH_150/152:

### endpoint only (was 500)
 endpoint: …/__probe2__ | events: ['taskCreated']        <- events preserved
### events round-trip (was 400)
 events: ['taskStatusUpdated', 'taskUpdated']
### status only
 health: {'fail_count': 0, 'status': 'active'}
### '*' now refused
 VALIDATION_ERROR
### nothing to update
 VALIDATION_ERROR

Throwaway webhook deleted after.

Note on the existing suite

cmd.TestParseSince fails on main at bbfcdff as well — timezone-dependent, unrelated to this change. Everything else passes.

Two things I did not touch

  • webhook list prints secret in full. It is the HMAC signing key. Worth redacting by default or gating behind a flag.
  • task list --list vs webhook create --list-id - same concept, two flag names.

`webhook update` could not change a webhook's endpoint. Three bugs, all in
the request body.

`Events` was typed `string`. ClickUp wants an array and answers
400 "Invalid events" (OAUTH_150) to a scalar — for the exact value its own
GET returns, so feeding `list` output back through `update` failed.

No field had `omitempty`, so an endpoint-only update still sent
`"status": ""` and `"events": ""`. ClickUp answers 500 (OAUTH_152) to the
empty status. The same update with those keys absent returns 200: PUT here
behaves like PATCH and preserves anything the body omits.

`--events '*'`, which the flag's own help recommended, is worse than
broken: ClickUp answers 200 and sets `events: []`. It unsubscribes the
webhook from everything instead of subscribing it to everything. Now
refused with an explanation, since a silent unsubscribe on a live webhook
has no symptom until the deliveries stop.

Also refuse an update with nothing in it rather than sending an empty body.

Verified against the live API on a throwaway webhook: endpoint-only,
events round-trip, status-only, '*' refused, empty refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pikonha
pikonha merged commit 927f9c5 into main Aug 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant