WebSocket Pipeline Handler
The WebSocket Pipeline Handler is an Enterprise-only feature. Please contact us to trial this or sign up for an Enterprise account.
The webSocketPipelineHandler is Zuplo's WebSocket handler. It proxies
WebSocket connections to your backend and optionally lets you run code at two
points in the connection's life:
- Message policies pass every WebSocket message through a pipeline of policy functions before forwarding it. Each policy can inspect, transform, or drop the message. Use this to redact sensitive fields, enforce a message schema, filter events, or add observability to real-time traffic.
- The
onOpenconnection hook runs once per connection, after the connection to your backend is established and before any message is relayed. Use it to gate connections, send a handshake message to your backend, or attach your own connection lifecycle listeners (such asclose).
With no policies and no onOpen hook configured, the handler is a transparent
passthrough — this is what the deprecated
webSocketHandler did, and it now resolves to this
handler.
Messages are intercepted in both directions, configured independently:
inboundpolicies process messages traveling from the client to your backend.outboundpolicies process messages traveling from your backend to the client.
The pipeline only intercepts message events. Connection lifecycle events are
handled automatically: when either side closes, the other side is closed, and
socket errors are logged and the connection is torn down. There is no dedicated
policy hook for close or error events — instead, use the
onOpen hook to attach your own listeners to
either socket.
Configuration
Use the webSocketPipelineHandler export and add an inbound and/or outbound
array under options.policies. Each entry points to an exported function in one
of your project's modules.
Code
Two different policies blocks
The policies object inside handler.options configures the message
policies that run on each WebSocket frame. This is separate from the route-level
policies block (the inbound array next to handler), which configures the
standard request policies — such as API Key
or Rate Limiting — that run once during
the initial connection upgrade.
The rewritePattern option behaves identically to the
WebSocket Handler, including
JavaScript string interpolation.
Writing a message policy
A message policy is an exported function that matches the following signature:
Code
| Parameter | Description |
|---|---|
data | The message payload. For text protocols this is a string; binary frames arrive in the platform's binary form (for example, an ArrayBuffer). For an inbound policy this is the message from the client; for an outbound policy it is the message from the backend. |
target | The destination socket the message is being forwarded to. For inbound this is the backend connection; for outbound it is the client connection. |
source | The socket the message originated from. Call source.send(...) to send a message back to the originator, such as an acknowledgement or error. |
request | The original ZuploRequest from the connection upgrade. |
context | The ZuploContext for the connection. |
The return value controls what happens next:
- Return the data (modified or unchanged) to forward it to
target. When multiple policies are configured, the return value is passed asdatato the next policy in the array. - Return
undefinedto drop the message. It is not forwarded, and no further policies run for that message.
Policies run in the order they appear in the inbound / outbound array, and
each policy may be asynchronous. If a policy throws, Zuplo logs the error and
drops the message.
Example: redact fields from inbound messages
This inbound policy parses each JSON message from the client, removes a sensitive field, and forwards the result to the backend.
Code
Example: filter outbound messages
This outbound policy inspects messages from the backend and drops internal events so they never reach the client. Other messages pass through unchanged.
Code
You can configure multiple policies in each direction to compose behavior — for example, one policy to validate a message against a schema and a second to redact fields. Because each policy receives the previous policy's output, order matters.
The onOpen connection hook
The onOpen option configures a function that runs once per connection,
after both the client and backend connections are established and before any
message is relayed. Unlike message policies, it isn't tied to a direction — it
receives both sockets.
Configure it at the options level, as a sibling of rewritePattern and
policies. It works with or without message policies:
Code
The hook matches the WebSocketOnOpenHook type exported from @zuplo/runtime —
the same parameters as a message policy, minus the message data:
Code
| Parameter | Description |
|---|---|
target | The backend connection. Call target.send(...) to send a message to your backend. |
source | The client connection. Call source.send(...) to send a message to the client. |
request | The original ZuploRequest from the connection upgrade. |
context | The ZuploContext for the connection. |
The hook is awaited, and its outcome controls the connection:
- Return (or resolve) to accept the connection. Message relaying begins only after the hook completes — messages that arrive while it runs are buffered and delivered afterward, in order.
- Throw (or reject) to abort the connection. Both sockets are closed and the
client never receives the
101 Switching Protocolsupgrade response.
Example: gate connections and greet the backend
This hook rejects unauthenticated connections, then sends a handshake message to the backend before any client traffic flows.
Code
Example: react to the connection closing
There is no separate onClose option — because onOpen hands you both raw
sockets, attach a standard close event listener to either one. Listeners
attached here live for the duration of the connection; there's no need to remove
them.
Code
The same pattern works for error and message events. For a listener that
should fire only once — such as waiting for a single handshake reply from your
backend — pass { once: true } as the third argument to addEventListener.
Performance
With no message policies and no onOpen hook, the gateway hands the connection
straight through to your backend and stays out of the per-message data path
entirely. Configuring onOpen (or any message policy) keeps the gateway in the
data path for that route, since the hook needs both live sockets to act on.