Skip to content

docs: add example how to configure custom webhook timeout - #1016

Open
Matcham89 wants to merge 3 commits into
agentgateway:mainfrom
Matcham89:custom-webhook-timeouts-docs
Open

docs: add example how to configure custom webhook timeout#1016
Matcham89 wants to merge 3 commits into
agentgateway:mainfrom
Matcham89:custom-webhook-timeouts-docs

Conversation

@Matcham89

@Matcham89 Matcham89 commented Sep 3, 2026

Copy link
Copy Markdown

Company

Not applicable.

Contribution summary

Example of how to add Timeouts to Custom Webhooks for Standalone Deployment

Logo file

Not applicable; no company logo is added.

Listing

Not applicable; no contributor listing is changed.

Preview

Reviewed the rendered standalone guide locally with `make build`.
Screenshot 2026-09-03 at 10 56 34 AM

@Matcham89
Matcham89 force-pushed the custom-webhook-timeouts-docs branch from 33739e9 to b0a7b5a Compare September 3, 2026 17:59
@kristin-kronstain-brown

Copy link
Copy Markdown
Collaborator

Thank you for working on this! The core mechanism the PR documents is correct and works. I confirmed it against the agentgateway source and by running it in a kind cluster with a real slow webhook. Two supporting sentences are wrong or misleading, and one important consequence of the change is missing.


Verification

Tested in a kind cluster using cr.agentgateway.dev/agentgateway:latest-dev, a Python webhook that sleeps 15 seconds and then returns a RejectAction, and real POST /v1/chat/completions requests.

Test Config Result
A no requestTimeout, webhook sleeps 15s 503 at 10.0sfailed to call prompt guard webhook: upstream call timeout
B requestTimeout: 35s, webhook sleeps 15s 403 at 15.03s — the webhook's reject action is honored
C requestTimeout: 5s, webhook sleeps 15s 503 at 5.02s
D backend: content-safety-webhook (no leading /) 503 in 3msservice not found
E webhook as a sidecar, host: localhost:8000, no Service 403 at 15.02s — works

Corroborating source evidence:

  • The 10-second default is real: crates/agentgateway/src/llm/policy/mod.rs:19-24 inserts BackendRequestTimeout(Duration::from_secs(10)) on every guardrail webhook request.
  • The backend policy overrides it: HTTP::apply (crates/agentgateway/src/types/backend.rs:23-27) re-inserts BackendRequestTimeout from apply_backend_policies, which runs after the guardrail default is set.
  • The leading / is genuinely required: local-config backends are keyed <namespace>/<name> (Backend::name, crates/agentgateway/src/types/agent.rs:1754-1772) and local_name gives them an empty namespace.

Good catch documenting the leading slash. agentgateway --validate-only accepts the slashless form and it only fails at request time, so this is exactly the kind of thing docs need to call out.


Blocking

1. "The backend host must include a port when the webhook does not use the default HTTP port." — this is wrong

The port is always required. Dropping it fails at startup:

Error: backends[0]: invalid host:port: content-safety-webhook.example.com

Suggested replacement:

The backend host must include a port.

2. The [!NOTE] doesn't belong on a standalone page, and its last sentence misleads

This file lives under content/docs/standalone/main/, which is the non-Kubernetes documentation.

More importantly, I ran agentgateway with the webhook as a sidecar container in the same pod, host: localhost:8000, with no Service defined anywhere. It worked (test E above). The sentence "A sidecar is reachable through localhost, but it is not automatically a Kubernetes Service and cannot be addressed by a Service name until you create one" is literally true, but it reads as "sidecars need a Service," which is the opposite of what a standalone reader should do.

Suggested replacement:

> [!NOTE]
> The backend host must be an address that agentgateway can reach, such as `localhost:8000` for a webhook running alongside the proxy.

If the Kubernetes Service guidance is worth keeping, it belongs in the Kubernetes guardrails guide, not here.


Should fix

3. Say why the named backend is needed

Per the config schema, webhook.target is a SimpleLocalBackendSerde with additionalProperties: false. It accepts host, service, or backend only, with no inline policies field. That is the entire reason for the indirection, and it is not stated.

Readers arriving from extAuthz will try the inline form first, because extAuthz does take policies next to host (see configuration/security/external-authz.md:249-260), and they will hit a validation error.

Suggested addition:

The webhook target does not accept inline policies, so the timeout must be set on a named backend that the target references.

4. Missing: what a timeout actually does to the request

A timed-out webhook is a webhook failure, so failureMode governs the outcome. The default is failClosed, which rejects the request with a 503 (confirmed in tests A and C). That is the single thing a reader tuning a timeout most needs to know, and this page never mentions failureMode at all.

Add a sentence and a pointer to the failure-mode behavior.

5. The leading-slash explanation is slightly off

The leading / in backend: /content-safety-webhook identifies a backend defined in the local standalone configuration.

The / is the namespace separator in the <namespace>/<name> reference form; local backends have an empty namespace.

Suggested replacement:

Backends are referenced as <namespace>/<name>. Backends defined in local configuration have no namespace, so the reference starts with /.


Nits

  • "define a named backend with an HTTP requestTimeout" reads as though a noun is missing. Suggest "define a named backend that sets requestTimeout".
  • The timeout applies per call. The request guard and the response guard each get their own 10 seconds; it is not a shared budget. Worth stating, since the example wires the same backend to both.
  • requestTimeout: 35s unquoted parses fine (test B), but external-authz.md:260 writes requestTimeout: "5s". Minor consistency call.

Thank you!

@kristin-kronstain-brown kristin-kronstain-brown left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requested changes above.

Signed-off-by: Matcham89 <cjmatcham@hotmail.co.uk>
@Matcham89
Matcham89 force-pushed the custom-webhook-timeouts-docs branch from 3e9186a to d3beb73 Compare September 4, 2026 14:13
Signed-off-by: Matcham89 <cjmatcham@hotmail.co.uk>
@Matcham89

Copy link
Copy Markdown
Author
Screenshot 2026-09-04 at 8 01 27 AM

@kristin-kronstain-brown

Copy link
Copy Markdown
Collaborator

Thank you for the quick turnaround. All of the items look addressed. In reviewing another time, a couple other things might be worth taking a look at.

1. action: audit does not protect traffic from a timeout

The note near the top of this page tells readers that webhook.action: audit "records what it detects and forwards the content unchanged." That is not true of a timeout. The failure branch runs before the audit check, so a reader who set action: audit to keep the guard non-blocking still gets a rejection:

action: audit, requestTimeout: 3s, webhook sleeps 12s  →  503 at 3.02s

With the new paragraph in place, the page now states both things and leaves the reader to reconcile them. One clause resolves it.

Suggested replacement for the paragraph after the configuration example:

The timeout applies separately to each webhook call, so request and response guards each receive their own timeout. A timeout is treated as a webhook failure. By default, failureMode is failClosed, which rejects the request, even when action: audit is set. Set failureMode: failOpen on the webhook to allow the request when the webhook times out or otherwise fails.

2. The section applies to latest as well as main

content/docs/standalone/latest/llm/prompt-guards/webhooks.md and the main copy are byte-identical today, and latest is the version that most readers land on. Everything the new section uses exists in that release: webhook failureMode, the backend: target form, and policies.http.requestTimeout are all in the v1.5.0 configuration schema.

Copy the new section into the latest page as well, so the two do not drift.

Other nits

The section below this one describes its fields in a | Setting | Description | table, and this section uses three paragraphs plus a note instead of a table. A table would match the page, and it would also let "the backend host must include a port" and the note about a reachable address collapse into a single row instead of sitting two paragraphs apart. That split is an artifact of two separate suggestions in my last review rather than anything you wrote, so treat this as optional. The section reads correctly as it stands.

Thank you again for chasing the details on this one. The leading-slash behavior and the timeout override are both things that only show up at request time, which makes them exactly the kind of thing the docs need to state.

Signed-off-by: Matcham89 <cjmatcham@hotmail.co.uk>
@Matcham89

Copy link
Copy Markdown
Author
Screenshot 2026-09-04 at 11 25 54 AM

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.

2 participants