feat(webhook): add opt-in retries to IncomingWebhook and WebhookTrigger#2641
Conversation
Add a new WebhookTrigger class that mirrors IncomingWebhook but handles
Workflow Builder webhook triggers which return JSON responses with
arbitrary payloads (vs plain text "ok" from incoming webhooks).
- Constructor takes URL + defaults (timeout, agent) — same pattern
- send() accepts arbitrary key-value payload, returns { ok, body }
- Reuses existing error infrastructure and User-Agent instrumentation
- Enables consumers like slack-github-action to use the SDK instead
of raw fetch for WFB triggers
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Workflow Builder webhook trigger inputs are always string values. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
# Conflicts: # packages/webhook/package.json # packages/webhook/src/index.ts
🦋 Changeset detectedLatest commit: 35d7096 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2641 +/- ##
==========================================
+ Coverage 88.97% 89.06% +0.09%
==========================================
Files 64 65 +1
Lines 10363 10436 +73
Branches 467 471 +4
==========================================
+ Hits 9220 9295 +75
+ Misses 1122 1120 -2
Partials 21 21
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Default the payload to an empty object so send() can be called with no
arguments, POSTing an empty body. send({}) continues to work unchanged.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Allow send() to be called with no arguments, POSTing an empty body.
send({}) continues to work unchanged.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…webhook-retries # Conflicts: # packages/webhook/src/WebhookTrigger.ts
The `= {}` default now lives in the WebhookTrigger PR; keep the @param
description unchanged here.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Use a falsy check so an empty-string URL is rejected up front rather than failing later at request time. Adds a guard test. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Use a falsy check so an empty-string URL is rejected up front rather than failing later at request time. Adds a guard test. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…s test
Add a test that a 401 with { ok: false, error: 'invalid_auth' } rejects
with an HTTPError exposing the response status and body. Remove the
success test asserting workflow_run_id so success cases only assert
result.ok === true.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
WebhookTrigger throws the same coded errors as IncomingWebhook but had no correspondingly named types. Add WebhookTriggerSendError / WebhookTriggerHTTPError / WebhookTriggerRequestError aliases and export them so consumers have properly-named errors to catch. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…source Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
The webhook clients have no request queue to honor a Retry-After header, so a 429 cannot respect the server's backoff on retry. Treat all 4xx responses uniformly and leave rate-limit handling to the caller; only 5xx responses are retried. Addresses review feedback on #2641. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
The webhook clients have no request queue to honor a Retry-After header, so a 429 cannot respect the server's backoff on retry. Treat all 4xx responses uniformly and leave rate-limit handling to the caller; only 5xx responses are retried. Addresses review feedback on #2641. Co-Authored-By: William Bergamin <25348381+WilliamBergamin@users.noreply.github.com> Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
56cf76e to
3d9a0af
Compare
…etry Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg
left a comment
There was a problem hiding this comment.
🔭 Duplicated comments from changed code!
|
|
||
| export { addAppMetadata } from './instrument'; | ||
|
|
||
| export { default as retryPolicies, RetryOptions } from './retry-policies'; |
There was a problem hiding this comment.
🎁 note: Here we match the export pattern of the @slack/web-api package:
There was a problem hiding this comment.
Praise for matching the pattern 🚀
Drop the { ok, body } wrapper so send() resolves to the parsed response
body itself, exposing ok/error without nesting or a duplicated ok field.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Assert that metadata added via addAppMetadata is sent in the User-Agent header on a WebhookTrigger request, alongside the base webhook agent. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…webhook-retries # Conflicts: # packages/webhook/src/WebhookTrigger.test.ts
send() now returns response.data directly; the string-vs-object parse branch was dead code since axios already parses JSON responses. Removes the now-unused AxiosResponse import. Simplifies the eventual fetch swap. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
The trigger endpoint responds with { ok: true } or { ok: false, error },
so type the result closed rather than an open index signature. Widening
later is backward-compatible; tightening a released `any` would not be.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…webhook-retries # Conflicts: # packages/webhook/src/WebhookTrigger.ts
The package now covers both Incoming Webhooks and Workflow Builder triggers, so the narrower "Incoming Webhooks" title is outdated. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
| if (error.response !== undefined) { | ||
| const status: number = error.response.status; | ||
| const wrapped = httpErrorWithOriginal(error); | ||
| throw status >= 500 ? wrapped : new AbortError(wrapped); |
There was a problem hiding this comment.
Nice 💯 thanks for simplifying this 🚀
|
|
||
| export { addAppMetadata } from './instrument'; | ||
|
|
||
| export { default as retryPolicies, RetryOptions } from './retry-policies'; |
There was a problem hiding this comment.
Praise for matching the pattern 🚀
| if (error.response !== undefined) { | ||
| const status: number = error.response.status; | ||
| const wrapped = httpErrorWithOriginal(error); | ||
| throw status >= 500 ? wrapped : new AbortError(wrapped); |
# Conflicts: # packages/webhook/README.md # packages/webhook/package.json # packages/webhook/src/WebhookTrigger.test.ts # packages/webhook/src/WebhookTrigger.ts # packages/webhook/src/index.ts
|
@WilliamBergamin Will do! 🫡 I look forward to bringing this to the Slack GitHub Action after an upcoming release. |
Bring the new @slack/webhook features from main into v8: - WebhookTrigger class for Workflow Builder triggers (#2615) - opt-in retries for IncomingWebhook and WebhookTrigger (#2641) Re-expressed on v8's fetch + Error-subclass architecture (main built them on axios + error-factory functions). Non-webhook release churn from main's parallel 7.x line (docs, web-api version bumps, CHANGELOGs) resolved to v8. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Summary
Adds opt-in retry support to
@slack/webhook, mirroring theretryConfigconvention already used by@slack/web-api'sWebClient. Stacked on top of #2615 (theWebhookTriggerclass), so this PR is scoped to just the retry change.IncomingWebhookandWebhookTriggeraccept a newretryConfig?: RetryOptionsconstructor option.retry-policies.tswith the same named policies exported by@slack/web-api—fiveRetriesInFiveMinutes,tenRetriesInAboutThirtyMinutes,rapidRetryPolicy— re-exported from the package entry point.send()is wrapped inp-retryusing the configured policy.Default: no retries (
{ retries: 0 }) whenretryConfigis unset — preserves today's behavior; retries are opt-in.Retry conditions (transient failures only):
5xx) → retry4xx), including rate limits (429) → abort immediatelyUnlike
@slack/web-api,429responses are not retried here. That client blocks its request queue to honor theRetry-Afterheader; the webhook clients have no such queue, so a blind retry would ignore the server's backoff. Rate-limit handling is left to the caller, and all4xxresponses are treated the same.retryConfigis a transport-only option and is stripped from the posted payload (likeagent).Notes for reviewers
does not retry a 429 even when a retry policy is setcases in bothIncomingWebhook.test.tsandWebhookTrigger.test.ts. The rationale is above (no request queue to honorRetry-After).@types/retryis pinned to0.12.0againstretry ^0.13.1— this matches@slack/web-api's own pinning.0.12.0is the latest published version of the type definitions, and theOperationOptionsinterface we extend forRetryOptionsis unchanged across0.12/0.13.retry-policies.tsmirrors@slack/web-api's rather than sharing it, matching the existing per-package convention in this monorepo.Consumed downstream by slackapi/slack-github-action#630, which passes its
0/5/10/RAPIDinput through to these policies.Requirements
🤖 Generated with Claude Code