Route-aware interception
Preview. Turn a Route’s Intercept toggle on, install the KnoxCall SDK’s interceptor once, and every call your code — or any third-party SDK inside it — makes to that Route’s upstream host goes through the Route. The Route injects the stored credential; the provider key never enters your process. Turn the toggle off and the traffic goes back to where it went before, on the next refresh, with no code change.How it works
- You opt a Route in.
intercept_enabledis a per-environment setting on the Route (the Intercept toggle on each environment card of the Route page, orintercept_enabledon the create, update and environment endpoints). It is off by default. Only one intercept-enabled Route may cover a given upstream host and base path in an environment — turning on a second is refused with409 intercept_route_conflict, because two Routes claiming the same traffic would be resolved by a tie-break nobody chose. - The SDK polls the manifest.
GET /v1/wrap/intercept-manifestlists, for one environment, every intercept-enabled Route’s upstreamhost,base_pathandslug. The SDK refreshes it every 60 seconds (jittered), and immediately when KnoxCall refuses a route-mode request — a401, or the404 route_not_foundan authenticated credential gets for a Route that no longer exists — so a Route created, enabled or disabled later is picked up without a restart. A404of typeenvironment_not_configuredorenvironment_disabledis surfaced as-is: a refresh cannot fix an environment. Your provider’s own 404s are never mistaken for either — they carryX-Knox-Upstream-Status. - Each outbound request is decided in a fixed order. The first matching rule wins:
Rule 5 before rule 6 is the whole feature: a host you listed is served by the ephemeral proxy until a Route covers it, upgrades to the Route the moment one does, and downgrades back when the Route is disabled.
Route mode
In route mode the SDK sends the request to your tenant’s data plane withx-knoxcall-route: <slug>, the path rebased under the Route’s base_path (a Route targeting https://api.hubapi.com/crm/v3 receives /objects/contacts for a request to /crm/v3/objects/contacts), and the query string intact. The request’s own Authorization header is dropped before it leaves — the Route injects the stored secret, so a placeholder token in the third-party SDK is exactly that. Everything a Route does applies: injected headers, per-environment overrides, rate limits, client requirements, logs and analytics. The API Log marks each call the SDK rerouted as SDK intercept (a direct call() shows as Direct), so you can see exactly which traffic the interceptor is carrying.
A request under a covered host but not under any covered base path is not rerouted; it falls to rule 6 (ephemeral if the host is listed, direct otherwise) and fires onUnmatchedPath once per host and path prefix so you can see it.
Ephemeral mode
A host you list inhosts that no Route covers goes through the ephemeral proxy in transit mode: the request’s own credential is carried on a KnoxCall header and the SDK’s KnoxCall token authenticates the hop. Per-host options put a host in escrow mode instead (credential: { secret: "<escrowed name>" }), or let transit go direct when KnoxCall is unreachable (unavailable: "direct").
When KnoxCall is unreachable
Route mode and escrow fail closed: there is no credential in your process to go direct with, so the SDK surfaces the connection error. Transit is the one mode that can opt out, per host, withunavailable: "direct"; it fires onFallback when it does.
What it is, and is not
- It is a convenience, not a security boundary. Each SDK patches the process-wide HTTP seam its language has (global
fetchandnode:httpin Node;httpxandurllib3in Python;http.DefaultTransportin Go;Net::HTTPin Ruby). PHP has no process-wide seam, so the SDK offers a route-aware PSR-18 client and a Guzzle middleware instead. Traffic that does not pass through that seam is not reached — each SDK page states exactly what is and is not covered. - The custody path is route mode. Ephemeral transit carries your own credential through KnoxCall; escrow and route mode keep it out of your process entirely.
- The manifest is a read. It needs
route:readon the SDK’s credential, the same permission as listing routes; it is not permission to escrow, mint or spend anything. - One kill switch, no deploy.
KNOXCALL_INTERCEPT=offmakes every interceptor and route-aware transport pass everything through, per process.
Which stacks are reached
Every SDK also offers the same decisions on an explicit transport you inject yourself (
wrap.fetch({ routes: "auto" }), wrap.transport(routes="auto"), Wrap.RoundTripper(WithRoutes()), wrap->httpClient(['routes' => 'auto']), wrap.faraday_connection(routes: :auto)), for code that would rather not patch process globals.
Uncovered egress
The interceptor already sees every outbound request the process makes, so it can also tell you which ones it did not intercept: calls that carried a credential to a host no Route covers, or to a host whose Route has interception switched off. Those are reported to KnoxCall and shown on Proxy → Uncovered egress in the dashboard, with one action per row — create a Route, or turn interception on for the one that already matches. What is sent. Per host, the first path segment (/ or /<one segment>), the HTTP method, the name of the header the credential travelled in (authorization, x-api-key, …) and how many calls, with first- and last-seen timestamps. Nothing else: no credential values, no full paths, no query strings, no request or response bodies, no client addresses. The server refuses an entry that carries anything beyond that shape, and the header name must be on a fixed allowlist of credential headers — so a value cannot be reported even by mistake. Some APIs put a key in the path itself (a Telegram bot token is /bot<id>:<token>/…), so a first path segment that looks like a credential — a known key prefix, a token, anything unusually long — is stored as / instead: the host is still listed, the value is not kept.
On by default. Every intercept() install reports unless told not to. Switch it off per install with observe_uncovered: false, or per process with KNOXCALL_OBSERVE_UNCOVERED=off; either stops the reporter entirely. KNOXCALL_INTERCEPT=off stops it too, since there is no interceptor to observe with.
Retention. Observations are kept per host and day for 30 days, then deleted. A workspace may add at most 5,000 distinct (host, path segment, method, header name) combinations per day; beyond that the SDK’s report is accepted and the excess is dropped. The endpoint is POST /v1/wrap/egress-observations and needs route:read, the same permission as the manifest.
Hooks
onReroute (before a KnoxCall send: host, URL, mode, slug), onRefresh (after a manifest change: added and removed hosts), onManifestError, onUnmatchedPath, onRefused (a route-mode refusal that triggered a refresh, and what the re-decision was), onFallback (a transit request went direct). Names follow each language’s casing.
See also
- What are Routes? and Creating routes
- Route environments —
intercept_enabledper environment - Go agent — the same manifest, for processes whose code you cannot change