> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Respond to Webhook

> Turn a workflow into an HTTP endpoint: hold the caller, run your steps, and answer with the status, headers and body you choose.

# 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](/essentials/routes/creating-routes) 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*:

| Field                     | What it does                                                                                                                         |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Webhook Response**      | *Reply immediately (202)* — the default and the behaviour every existing webhook has. *Respond from the workflow* — hold the caller. |
| **Response Timeout (ms)** | How long to hold, between 250 ms and 30 000 ms. Defaults to 10 000 ms.                                                               |

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:

```json theme={"dark"}
{ "success": true, "execution_id": "…", "status": "accepted" }
```

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](/workflows/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.

## Related

* [Triggers](/workflows/triggers)
* [Creating workflows](/workflows/creating-workflows)
* [Storage](/workflows/storage)
