Skip to main content
Portkey is an AI gateway that routes model requests to providers and handles credentials, retries, caching, and logging. Its Bring Your Own Guardrails (BYOG) webhook can call TrustGuard before a provider request and after a model response. Portkey and TrustGuard use different webhook contracts, so the integration requires a small adapter. Portkey sends a hook event and expects a boolean verdict; the adapter sends the relevant request or response content to POST /v1/evaluate and maps TrustGuard’s status to that verdict. Only traffic routed through a Portkey configuration is evaluated. Direct calls to a model provider bypass this integration.

Integration capabilities

Policy can also apply across the turns of a conversation rather than to one message at a time, which requires the caller to send a session key — see Group the turns of a conversation.

Before you start

Start with the policy in Observe mode. Findings appear in Activity without affecting traffic. Switch to Enforce after reviewing the results. See Policies.

1. Implement the webhook adapter

Portkey sends a JSON object containing request, response, metadata, and eventType. For beforeRequestHook, evaluate request.json as input. For afterRequestHook, evaluate response.json as output. Use the corresponding text field when a normalized JSON body is not available. The following function shows the contract conversion. Deploy the equivalent in your webhook service and return its result as JSON:
Sending request.json as payload is what puts the caller’s whole messages array in front of the policy, so the evaluation is not limited to the newest message. See Group the turns of a conversation. Do not point default.webhook directly at /v1/evaluate. Portkey’s hook event contains top-level fields that the Evaluate API does not accept, and the Evaluate API returns status rather than Portkey’s required verdict. The sample maps only block to verdict: false. It allows allow, report, ask, and transform. It does not return transformedData, so redaction is not supported. Use Block rules for enforcement.

2. Configure the BYOG hooks

Add default.webhook to both hook phases. Replace the example adapter URL and token with your values:
deny: true makes a failed check return HTTP 446. async: false keeps the check in the request path so Portkey can enforce its result. With only the before-request hook, Output-phase rules do not run. Prefer a saved Portkey configuration when the webhook header contains a secret. An inline configuration sent through x-portkey-config exposes that value to the calling application.

3. Group the turns of a conversation

A multi-turn attack spreads its intent across several messages. Each one reads as harmless on its own; the escalation, the reinforcement, or the reassembled instruction exists only across turns. See multi-turn attacks for the techniques involved — Crescendo, Echo Chamber, Multi-Turn Manipulation, and Payload Splitting. Their common target is a check that inspects each message independently, which is what the adapter does until you give it the conversation. Two inputs give TrustGuard the conversation, and they are complementary rather than alternatives: Neither happens by default. /v1/evaluate synthesizes a session_id when the field is omitted, so a gateway whose callers send no conversation key produces one session per request. Nothing errors and no finding goes missing, so the gap is only visible as single-turn conversations in Activity.

Send a session ID through metadata

Portkey has no conversation identifier of its own, so the caller — or, better, an authenticated proxy in front of Portkey — supplies one on every request:
Portkey forwards this object to the webhook as event.metadata, on both hook phases. _user is Portkey’s reserved user identifier, and session_id is the key its own metadata guidance recommends for grouping related requests, though the gateway itself does nothing with it: only your adapter reads it. The adapter maps both onto the Evaluate API body, as shown in step 1. It coerces each to a string because Portkey parses the header without validating value types, so a caller can put a number where a string is expected.
Metadata is caller-supplied, so a client can pick its own session_id and _user. Have your authentication layer set or overwrite both before the request reaches Portkey if either drives a policy decision. Metadata attached to a workspace or an API key also takes precedence over per-request metadata, so a session_id or _user set at either level silently overrides the value the caller sends.
Do not reuse x-portkey-trace-id as the conversation key. Portkey generates one per request when the caller omits it, it identifies a request rather than a conversation, and it does not reach the webhook body at all.

Give one call the history

Portkey does not store turns, but it does not need to: event.request.json is the request body as Portkey parsed it, so on a /v1/chat/completions call it already holds the caller’s complete messages array. Forwarding it as payload — which the step 1 adapter does — puts the whole conversation in one evaluation call at no extra cost.
event.request.text is only the last message, not the transcript. An adapter that falls back to { input: source.text } when request.json looks empty silently discards every earlier turn. Treat an empty request.json on a chat completion as an error worth logging rather than a reason to degrade: it means the body arrived in a form Portkey did not parse.
The transcript grows every turn, so account for the payload size in the webhook timeout and in your token budget. The afterRequestHook event carries request.json alongside response.json, so an adapter can send the transcript and the completion together on the output leg rather than the completion alone. The sample sends only the completion, which is the smaller payload; send both if an Output-phase rule needs to judge the answer against the conversation that produced it.

4. Configure failure behavior

Portkey’s webhook timeout defaults to 3000 ms. The example raises it to 5000 ms and sets failOnError: true, so a timeout or non-200 adapter response fails the check. With deny: true, Portkey returns 446 instead of calling the provider or returning the completion. Without failOnError: true, Portkey allows the request when the webhook times out or returns an error. Use that setting only when you deliberately want to fail open. The adapter should return a non-200 response when TrustGuard is unavailable or rejects its credentials. Returning { "verdict": true } in those cases makes the integration fail open regardless of the Portkey setting.

5. Verify

Attach the configuration to a request using your existing Portkey client or the x-portkey-config header. With a blocking rule in Enforce mode:
  1. Send a prompt that triggers the rule.
  2. Confirm that Portkey returns HTTP 446 and that hook_results contains the trustguard-input hook and default.webhook check.
  3. Confirm that the adapter’s data appears under the check’s responseData, including the TrustGuard trace_id.
  4. Find the same trace_id in TrustGuard Activity under the expected consumer_id and session_id.
Then send a request that passes and triggers an Output-phase rule. A blocked output also returns 446, but the provider has already processed the request.

Reference

Coverage

The adapter evaluates Portkey’s normalized request or response body. Tool definitions and model-selected tool calls are available only when Portkey includes them in that body, and a block stops the complete request or response, not an individual tool event. The adapter does not apply transformed payloads. Portkey does not support blocking output guardrails on streamed responses, so use non-streaming responses when output enforcement is required. Have the adapter skip its TrustGuard call on the response leg of a streamed request, using event.request.isStreamingRequest or event.request.json.stream. Portkey can still invoke the after-request hook on a stream, with no assembled response body to inspect. An adapter that evaluates it anyway posts an empty output payload under a real session_id on every streamed turn, which pollutes the conversation grouping it is trying to establish. A turn with both synchronous hooks makes two TrustGuard evaluations. Each hook also passes through the adapter, so include both network legs in the timeout and latency budget.

What is evaluated

Configuration

Attributes

  • consumer_id: map it from the _user value in verified Portkey metadata. Per-consumer policy routing and gates matching consumer.id depend on this value.
  • session_id: map it from a stable, verified session_id value in Portkey metadata, on both hook phases. Without it, /v1/evaluate synthesizes a session per request, which breaks the grouping the stateful detectors rely on. See Group the turns of a conversation.
  • attributes.model.name and attributes.model.provider: the adapter can read these from request.json.model and Portkey’s provider field.
  • trace_id and request_id: return them in the webhook response’s data object. Portkey records that object under hook_results[].checks[].data.responseData.
Do not trust caller-supplied metadata for policy decisions unless your authentication layer validates or replaces it.

Troubleshooting

Experimental: a native neuraltrust plugin is proposed in Portkey-AI/gateway#1772, but it is not part of a released Portkey version.