Cookbook: Proxy the OpenAI Realtime API
- Applies to
- Gatewayruntime for API, AI, and MCP gatewaysEnterprise add-on
- Hosting
- Managed EdgeManaged DedicatedNot availableSelf-HostedNot available
The OpenAI Realtime API
streams speech and text over a WebSocket. Server-side clients authenticate with
an Authorization: Bearer header that carries an OpenAI API key. This cookbook
puts a Zuplo route in front of that WebSocket so that:
- Clients authenticate with a Zuplo API key, and your OpenAI key stays on the gateway.
- The gateway sets the model in the backend URL, so the model a client puts in its own URL has no effect.
- A message policy ends any session in which the client sends its own
instructions or a
systemmessage.
The route uses the WebSocket Pipeline Handler, which is an Enterprise feature.
Prerequisites
- A Zuplo project on Managed Edge.
- An OpenAI API key with access to a Realtime model.
- An API Key Authentication consumer for each client that connects through the gateway.
1. Store the OpenAI key and model
Add two environment variables to your project:
| Name | Value | Secret |
|---|---|---|
OPENAI_API_KEY | Your OpenAI API key | Yes |
OPENAI_REALTIME_MODEL | The Realtime model clients use, such as gpt-realtime | No |
2. Add the policies
Add an API Key policy to authenticate clients, and a Set Headers policy that
replaces the client's Authorization header with your OpenAI key. Add both to
config/policies.json:
config/policies.json
The API Key policy runs first and reads the client's Zuplo key from the
Authorization header. The Set Headers policy then overwrites that header, so
OpenAI receives your OpenAI key and never sees the client's key.
3. Write the message policy
Create modules/realtime-policies.ts. This inbound policy reads each event the
client sends. Clients can change instructions in several ways: session.update,
response.create with its own instructions, or a conversation.item.create
that adds a system message. The policy checks every event for an
instructions field or a system-role item at any depth. When it finds one, it
closes the client connection, and the handler then closes the connection to
OpenAI. Every other event passes through unchanged.
modules/realtime-policies.ts
4. Add the route
Add a GET route that uses the WebSocket Pipeline Handler. The rewritePattern
sets the model from the environment variable, so the model a client puts in its
own URL has no effect.
config/routes.oas.json
The route-level policies run once, when the client opens the connection. The
policies inside handler.options run on every message. See
Two different policies blocks.
5. Connect a client
Connect to your gateway URL with a Zuplo API key instead of an OpenAI key. This
Node.js example uses the ws package:
Code
Replace my-gateway.zuplo.app with your gateway's hostname. A request without a
valid Zuplo API key gets a 401 response and no WebSocket connection. A client
that sends an event with instructions, such as session.update or
response.create, receives the error event and then a close with code 4003.
Next steps
- Add a Rate Limiting policy to the
route-level
inboundarray to limit how many sessions each consumer opens. - Run code when a session starts with the
onOpenconnection hook. - Read more about message policies in the WebSocket Pipeline Handler reference.