Skip to main content

Respond to Webhook

A webhook trigger normally answers straight away: KnoxCall accepts the delivery, queues the run and returns 202 Accepted. That is the right behaviour for a provider firing events at you — they do not want your answer, they want an acknowledgement. Sometimes the caller does want an answer. A form that needs a validation result. An internal service that wants a lookup. A partner integration that expects JSON back. Respond from the workflow is that mode: the request is held open while the run executes, and a Respond to Webhook step decides what the caller receives. Pair it with a Route for authentication, rate limits and egress control, and you have an API endpoint you built on a canvas.

Turning it on

On the Trigger node, with Trigger Type set to Webhook: Switching an existing webhook to Respond from the workflow does not change its URL, its token or its authentication.

The Respond to Webhook step

Add a Respond to Webhook step anywhere in the graph. It sets three things:
  • Status Code — any code from 200 to 599.
  • Response Headers — a key/value list. A row can take its value from a Secret, which is how you sign your own response.
  • Body — JSON (validated before it is sent), plain text, or none.
Everything supports {{expressions}}, so a body can be built from earlier steps — the status code and the Content-Type included. A status expression must resolve to a number between 200 and 599; if it does not, the step fails and you see the error on the run rather than a caller quietly receiving a 200.

One answer per run

A run answers its caller once. The first Respond to Webhook step to execute wins. So a workflow may hold more than one reply step, but only where the two can never run in the same execution. When you publish, KnoxCall checks exactly that, and it has to be able to prove it. Three shapes count as proof:
  • the true and false arms of a Condition;
  • two different routes of a Router in first match mode;
  • a step’s success arm and its error arm, when that step is set to Route errors to the error output. (200 when the upstream call worked, 502 when it did not.)
Anything else is refused, because anything else runs both. Two reply steps hanging off the same step run both — a plain fan-out is parallel, not a choice. So does a Router in all matches mode, an unlabelled Router branch (it follows every match), two steps on the same arm of a Condition, and two reply steps in disconnected parts of the canvas. A second reply step reachable after the first, and a reply step inside a loop body, are refused for the same reason. In each case one of the replies would be dead configuration and you would only find out from a caller. If you want two replies where the choice is made by a pass-only-if filter on the edges, put a Condition in instead — a filter is not something the publish check can prove exclusive.

If the run takes too long

If the window closes before a Respond to Webhook step is reached, the caller receives 202 Accepted with the execution id:
The run is not cancelled — it carries on to completion, and you can look it up by that execution id. Design your endpoint so the fallback is acceptable: a caller that waited 10 seconds and got a 202 should still be able to find out what happened.

What a reply may and may not contain

Two rules, and both exist because this endpoint answers from a KnoxCall domain: Headers are allowlisted. Content-Type, Cache-Control, ETag, Retry-After, Link, Content-Disposition and friends are allowed, as is any header name of your own beginning x-. Refused: Set-Cookie, Location, Authorization, CORS Access-Control-* headers, hop-by-hop headers, and the platform’s own security headers. A workflow may not set a cookie on our origin, redirect from it, or relax its CORS policy. A disallowed header is reported when you publish, not silently dropped later. The body is redacted; a header value is not. Any Secret this run loaded is replaced with [REDACTED] in the response body before the bytes leave KnoxCall — a body is assembled out of run data, so a credential can end up there by accident. A header value is a slot you chose deliberately, so it is sent as written; that is what makes a Secret-backed signature header work. Do not template a Secret into the body and expect it to arrive. Replies are capped at 256 KB and 24 headers. A webhook reply is an answer, not a file transfer.

Repeated and identical requests

An endpoint answers every request. Two callers asking the same question with the same body — and one caller retrying after the 202 above — each get their own run and their own reply. That is deliberately different from a plain webhook. In Reply immediately mode a byte-identical redelivery inside five minutes is treated as the provider retrying an event it already sent us, and is collapsed to a single run; that is what stops a replayed Stripe delivery re-running your workflow. An endpoint is not an event feed, so the same rule would break it: the second caller would get 202 duplicate and no answer at all. Idempotency of the work is therefore yours, exactly as it is for any HTTP API you write. If a request must be safe to retry, have the caller send a key of its own (a header or a body field), and use a Storage step to record it and short-circuit the second run.

Rate limits

Because a workflow-backed endpoint launches a run on every request, it counts against your workspace’s workflow execution allowance — the same budget the dashboard, the API and every other launch path spend. A request over the limit is answered 429 with a Retry-After header, and no run is started. Retries of the same request count too, which is the bound that replaces the redelivery collapse described above.