JSON Schema Validation: Kong vs Envoy

Kong and Envoy both validate request bodies, and they do it in ways different enough that the choice affects latency, failure behaviour and who owns the schema. Kong runs a Lua plugin inside the worker; Envoy sends the request to an external processor over gRPC, or runs a WebAssembly module in-process. This page compares the two on the dimensions that matter once it is live.

Prerequisite concepts

This assumes the model in request validation and schema enforcement and the execution-model differences described in Kong vs Tyk vs Envoy for microservices.

Where the validation runs

In the worker, or across a socket Kong validates inside the worker process, so the cost is CPU and nothing else can fail. Envoy's external processor sends the buffered body to a separate service over gRPC, adding a round trip and a dependency, in exchange for validation logic written in any language and deployed on its own schedule. Kong 3.x — in the worker request → Lua validator → upstream tens of microseconds, no extra process to fail Envoy 1.32+ — external processor Envoy buffers validator service any language, own deploy a gRPC round trip per request, and a dependency with a failure mode WebAssembly sits between the two: in-process like Kong, language-flexible like the external processor.

The failure mode is the real difference

Kong’s plugin cannot be unavailable independently of the gateway — if the worker is running, validation is running. Envoy’s external processor can be down, slow, or overloaded while Envoy itself is healthy, and what happens then is a configuration decision you must make.

A timeout above the median is a timeout that fires constantly With a validator whose median latency is forty milliseconds and whose ninety-ninth percentile is two hundred and fifty, a two hundred millisecond timeout fails roughly one request in fifty. Raising the timeout to four hundred milliseconds covers the tail, and still sits well inside a three second route budget. validator latency distribution against two timeout choices p50 — 40 ms p99 — 250 ms timeout 200 ms — cuts the tail off, so ~2% of requests fail the validator call timeout 400 ms — covers the tail, and still only 13% of a 3 s route budget When the timeout fires, failure_mode_allow decides the outcome for every affected request at once — so a timeout set from the median converts an ordinary latency tail into either an outage or a validation bypass.
# Envoy 1.32+ — the two settings that define behaviour under validator failure
- name: envoy.filters.http.ext_proc
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
    grpc_service:
      envoy_grpc: { cluster_name: validator }
      timeout: 0.2s               # must be well inside the route budget
    failure_mode_allow: false     # false: reject when the validator is unavailable
    processing_mode:
      request_header_mode: SEND
      request_body_mode: BUFFERED
      response_header_mode: SKIP
      response_body_mode: NONE

A 200 ms timeout on a route with a 3 s budget sounds safe and is not, if the validator’s own p99 is 250 ms: every request then fails the validator call and the failure_mode_allow setting decides the outcome for all of them at once. Size the timeout above the validator’s tail latency, not above its median.

Comparing them on the dimensions that matter

Six dimensions, and they do not all point the same way Kong wins on added latency and on having no separate component to fail. The external processor wins on language choice, on deploying validation independently of the gateway, and on being able to hold logic too complex for a schema. Schema ownership differs: plugin configuration versus a service repository. dimension Kong plugin Envoy ext_proc added latency µs a round trip can fail independently no — same process yes — needs a failure mode language Lua, or JSON Schema config anything that speaks gRPC deploys with the gateway config its own release cycle logic beyond a schema awkward natural schema lives in plugin configuration a service repository

Decision matrix

Situation Choose
Plain JSON Schema, tight latency budget Kong plugin
Validation logic that needs a library external processor
Team cannot operate another service Kong plugin, or WASM
Validation must deploy without a gateway change external processor
Very high request rate, small payloads in-process, either product
Rules shared with a non-gateway consumer external processor

Gotchas and failure signals

An external processor on the request path is an upstream. It needs the same treatment as any other: a timeout below the route budget, a circuit breaker, capacity planning and its own dashboard.

Buffering happens on both, so the memory arithmetic is identical regardless of where the logic runs. The processor choice does not change the need for a size cap.

Kong’s plugin returns its own error shape, and the external processor returns whatever you write. Standardising the error body across routes is work either way, and it is worth doing once rather than per service.

A WASM module is in-process but not free. It has its own memory limit and a startup cost per worker, and a module that panics takes the request with it.

Who owns the schema, in practice

The dimension that decides this most often is not technical at all: it is which team is expected to change a validation rule and how quickly.

With the plugin model the schema lives in gateway configuration, which usually means a platform-team repository and a platform-team review. That is exactly right when validation is a platform concern applied uniformly, and it is friction when a product team needs to add an optional field on a Tuesday afternoon.

With the external processor the schema lives with the validator service, which the owning team deploys on its own cadence. That removes the friction and introduces a coordination question instead: a service that tightens its schema without telling anyone can start rejecting traffic that the gateway configuration says should be accepted, and the gateway’s own change log will show nothing at all.

Neither problem is unsolvable; both are worth deciding before the first schema ships, because retrofitting an ownership model onto forty routes is considerably harder than choosing one for the first.

Validation

  • Validator latency measured at p99, and the timeout set above it
  • failure_mode_allow chosen per route and exercised with the validator stopped
  • Body size cap present regardless of which implementation is used
  • Error body shape identical across routes and implementations
  • External processor has its own dashboard, alerts and capacity plan
  • Schema source of truth identified, with a CI check that it matches the service

FAQ

Which one adds less latency?

Kong, by a wide margin, because validation runs inside the worker process and costs tens of microseconds for a typical payload. Envoy’s external processor sends the buffered body to a separate service over gRPC, so the added latency is a round trip plus the validator’s own processing. WebAssembly sits in between: in-process like the plugin, but with language flexibility closer to the external processor.

What happens when an external validator is down?

Whatever failure_mode_allow says, which is why it must be set deliberately per route. False rejects traffic while the validator is unavailable; true lets unvalidated requests through. For a validator that is the only thing between the internet and an unhardened service, false is right. For one that mainly improves error messages, true is. Either way, exercise it with the validator stopped before you rely on the answer.

How should I size the external processor timeout?

Above the validator’s tail latency, not above its median, and well inside the route’s overall budget. A 200 millisecond timeout against a validator whose p99 is 250 milliseconds means a meaningful share of requests fail the call, and the failure mode then decides the outcome for all of them at once — which looks like a gateway problem rather than a timeout that was set too tightly.

Does the choice change how much memory validation needs?

No. Both implementations require the body to be buffered before it can be checked, so the memory arithmetic — payload size multiplied by concurrency — is identical. A request size cap is required regardless of where the validation logic runs, and it is the control that makes either option safe to enable.


Parent: Request Validation & Schema Enforcement