Skip to content

Add allowPauseResponse option to sync webhooks - #2451

Open
ravikiranvm wants to merge 7 commits into
mainfrom
rk/ops-4578-respond-on-pause-sync-webhook
Open

Add allowPauseResponse option to sync webhooks#2451
ravikiranvm wants to merge 7 commits into
mainfrom
rk/ops-4578-respond-on-pause-sync-webhook

Conversation

@ravikiranvm

@ravikiranvm ravikiranvm commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Part of OPS-4578.

Additional Notes

Bug: when the child Slack/Teams notification workflow failed before actually sending the message, the parent campaign workflow still marked the opportunity as Assigned. Originating incident: the child's "Format message" code step threw, so the flow never reached the Request Action step — no message was sent, yet the campaign table row was flipped to Assigned (and, being no longer Created, was never retried). Root cause: notify-owners called the child via the async webhook endpoint, which returns 200 on enqueue — so the "Webhook succeeded?" branch passed regardless of what happened in the child run.

Fix — three pieces:

  1. ProgressUpdateType.WEBHOOK_RESPONSE_ON_PAUSE (packages/shared): runs started with it get their synchronous webhook response published as soon as the run pauses, in addition to the existing terminal-state responses (FAILED → 500 etc.). Key insight: the Slack/Teams "Request Action" step sends the message and then pauses the run waiting for user action — so run paused ⇔ message delivered. Publish-on-pause is strictly opt-in: plain WEBHOOK_RESPONSE sync calls behave exactly as before (regression-tested), so sync flows that pause on delay steps still receive their final response after resume. The paused response body is the block-provided pauseMetadata.response when set, otherwise { message: 'The flow is paused' }.
  2. /v1/webhooks/:flowId/sync?allowPauseResponse=true: the sync route accepts the opt-in boolean allowPauseResponse parameter and starts the run with the new progress-update type. Validated by the route schema: true/false are accepted (omit the parameter or pass false to wait for completion); any other value is rejected with 400 before the run starts.
  3. [Will be part of internal PR]Templates notify-owners, remind-owners, escalate-unaddressed-opportunities call the child notification workflow via /sync?allowPauseResponse=true. The existing "Webhook succeeded?" (2xx) branches now mean "message delivered (child paused) or flow completed": child fails before sending → 5xx → the row is left untouched and retried next scheduler cycle. remind/escalate had the same defect class (recording Last notified at / Escalated at on enqueue) — same one-line fix, same mechanism.

Testing Checklist

Check all that apply:

  • I tested the feature thoroughly, including edge cases

  • I verified all affected areas still work as expected

  • Automated tests were added/updated if necessary

  • Changes are backwards compatible with any existing data, otherwise a migration script is provided

Visual Changes (if applicable)

N/A — the only user-visible text change is the Catch Webhook trigger description, which now documents /sync and /sync?allowPauseResponse=true.

Sync webhook callers can opt in via ?respondOnPause=true to receive the
response as soon as the flow pauses (e.g. a Request Action step waiting
on the user) instead of only on a terminal state. Plain /sync behaviour
is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 26, 2026 11:40
@linear

linear Bot commented Aug 26, 2026

Copy link
Copy Markdown

OPS-4578

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in mode for synchronous webhooks to return a successful HTTP response as soon as a flow pauses (instead of only on terminal states), enabling “message delivered” semantics for notification sub-flows that pause waiting for user interaction.

Changes:

  • Introduces ProgressUpdateType.WEBHOOK_RESPONSE_ON_PAUSE to support publishing synchronous webhook responses on PAUSED as well as terminal statuses.
  • Adds /v1/webhooks/:flowId/sync?respondOnPause=true wiring through the webhook controller/handler to start runs with the new progress update type.
  • Updates engine update-run publishing logic and adds unit tests + webhook trigger documentation.

Blocking

  • The /sync route’s respondOnPause querystring is defined as Type.Boolean, but the API server Ajv config uses coerceTypes: 'array', so query params won’t be coerced from 'true'/'false' strings to booleans in production. This can cause /sync?respondOnPause=true to be rejected (or mis-parsed), which breaks the primary feature of the PR.

Non-blocking

  • Consider returning pauseMetadata.response (when present) for PAUSED responses instead of always returning a hard-coded { message: 'The flow is paused' }, since the response field already exists and is set by some blocks.

Merge recommendation

Do not merge

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/shared/src/lib/engine/types.ts Adds a new progress update type to enable publish-on-pause behavior.
packages/server/api/src/app/webhooks/webhook-handler.ts Passes respondOnPause through to run start via progressUpdateType.
packages/server/api/src/app/webhooks/webhook-controller.ts Adds respondOnPause query param on /sync and updates route description.
packages/server/api/src/app/workers/engine-controller.ts Publishes webhook response on PAUSED when opted-in; adds PAUSED response handling.
packages/server/api/test/unit/webhooks/webhook-handler.test.ts Verifies progressUpdateType selection when respondOnPause is enabled/omitted.
packages/server/api/test/unit/webhooks/webhook-controller.test.ts Verifies /sync query param handling and async route behavior.
packages/server/api/test/unit/engine/engine-controller.test.ts Adds coverage for pause publishing behavior and ensures non-opt-in runs don’t publish on pause.
packages/blocks/webhook/src/lib/triggers/catch-hook.ts Updates webhook trigger docs to mention /sync and /sync?respondOnPause=true.
Suppressed comments (1)

packages/server/api/src/app/webhooks/webhook-controller.ts:89

  • The sync route querystring schema uses Type.Boolean, but the API server’s Ajv config uses coerceTypes: 'array' (see packages/server/api/src/app/server.ts:40), so query params aren’t coerced from 'true'/'false' strings into booleans. This will cause /sync?respondOnPause=true to be rejected as 400 in production. Consider accepting a string and doing a strict parse (=== 'true') in the handler.
  respondOnPause: Type.Optional(
    Type.Boolean({
      description:
        'When true, the response is sent as soon as the flow pauses (e.g. a step waiting for user action), instead of waiting for the flow to reach a terminal state.',
    }),

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

}>,
reply,
) => {
const respondOnPause = request.query.respondOnPause ?? false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route schema declares respondOnPause as Type.Boolean() and the Fastify AJV config (server.ts, coerceTypes: 'array') coerces "true"/"false" to booleans before the handler runs, so request.query.respondOnPause is already a boolean here; ?? false only covers the omitted-param case. Covered by webhook-controller.test.ts: =false → handler receives false, =banana → 400 and the handler is never called.

Comment thread packages/server/api/test/unit/webhooks/webhook-controller.test.ts Outdated
Comment thread packages/server/api/src/app/workers/engine-controller.ts
Comment thread packages/server/api/test/unit/engine/engine-controller.test.ts
ravikiranvm and others added 2 commits August 26, 2026 17:30
Use pauseMetadata.response as the body when a block sets it, falling
back to the generic paused message otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the boolean respondOnPause query parameter with a waitUntil enum
(completed | paused) so the option names the point at which the /sync
wait ends and leaves room for further modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ravikiranvm ravikiranvm changed the title Add respond-on-pause option to sync webhooks Add waitUntil option to sync webhooks Aug 26, 2026
ravikiranvm and others added 3 commits August 26, 2026 19:03
Drop the no-op completed value: omitting the parameter already waits for
completion, so a single literal keeps the API surface minimal while
leaving room to grow into a union later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the internal respondOnPause boolean with the caller-facing
waitUntil option so the request is traced under one name; the mapping to
ProgressUpdateType.WEBHOOK_RESPONSE_ON_PAUSE now lives in one place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread packages/server/api/src/app/webhooks/webhook-controller.ts Outdated
@ravikiranvm ravikiranvm changed the title Add waitUntil option to sync webhooks Add allowPauseResponse option to sync webhooks Aug 27, 2026
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants