> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # RelayClient > WebSocket client for real-time call and message control. [call]: /docs/server-sdks/reference/python/relay/call [message]: /docs/server-sdks/reference/python/relay/message [connect]: /docs/server-sdks/reference/python/relay/client/connect [disconnect]: /docs/server-sdks/reference/python/relay/client/disconnect [run]: /docs/server-sdks/reference/python/relay/client/run [dial]: /docs/server-sdks/reference/python/relay/client/dial [sendmessage]: /docs/server-sdks/reference/python/relay/client/send-message [receive]: /docs/server-sdks/reference/python/relay/client/receive [unreceive]: /docs/server-sdks/reference/python/relay/client/unreceive [execute]: /docs/server-sdks/reference/python/relay/client/execute `RelayClient` manages a persistent WebSocket connection to SignalWire's Relay service. It handles authentication, automatic reconnection with exponential backoff, inbound event dispatch, outbound dialing, and SMS/MMS messaging. Use it when you need imperative, event-driven control over calls rather than the declarative AI agent approach. The client supports two authentication modes: project ID + API token, or JWT token. Credentials can be passed directly or read from environment variables. ## **Properties** **`project`** `str` SignalWire project ID. Set via constructor or `SIGNALWIRE_PROJECT_ID` environment variable. --- **`token`** `str` API token for authentication. Set via constructor or `SIGNALWIRE_API_TOKEN` environment variable. --- **`jwt_token`** `str` JWT token for alternative authentication. Set via constructor or `SIGNALWIRE_JWT_TOKEN` environment variable. When provided, `project` and `token` are not required. --- **`host`** `str` — default: relay.signalwire.com Relay WebSocket endpoint. The default is the endpoint for SignalWire projects; you do not set your space here. The constructor argument wins, then the `SIGNALWIRE_SPACE` environment variable, then the default. `RestClient` reads `SIGNALWIRE_SPACE` as the REST host, so when it is set for REST, pass `host="relay.signalwire.com"` to keep the default. --- **`contexts`** `list[str]` — default: \[] List of contexts to subscribe to for inbound call and message events. --- **`max_active_calls`** `int | None` — default: 1000 Maximum number of concurrent inbound calls the client will track. Calls arriving beyond this limit are dropped with a log warning. Set via constructor or `RELAY_MAX_ACTIVE_CALLS` environment variable. Constructor-only -- not accessible as a public attribute after initialization. --- **`relay_protocol`** `str` Server-assigned protocol string from the connect response. Read-only. Used internally for session resumption on reconnect. --- ## **Decorators** ### on\_call ```python {10-11} from signalwire.relay import RelayClient from signalwire.relay.call import Call client = RelayClient( project="your-project-id", token="your-api-token", contexts=["default"], ) @client.on_call async def handle_call(call: Call) -> None: await call.answer() print(f"Received call: {call.call_id}") client.run() ``` Register the inbound call handler. The decorated function is called once for each `calling.call.receive` event on the subscribed contexts. The function receives a [`Call`][call] object with all call properties and control methods. Only one call handler can be active at a time -- calling `@client.on_call` again replaces the previous handler. ### on\_message ```python {10-11} from signalwire.relay import RelayClient from signalwire.relay.message import Message client = RelayClient( project="your-project-id", token="your-api-token", contexts=["default"], ) @client.on_message async def handle_message(message: Message) -> None: print(f"Received message: {message.body}") client.run() ``` Register the inbound SMS/MMS message handler. The decorated function is called for each `messaging.receive` event. The function receives a [`Message`][message] object with message properties and state tracking. Only one message handler can be active at a time -- calling `@client.on_message` again replaces the previous handler. ## **Methods** #### [connect](/docs/server-sdks/reference/python/relay/client/connect) Establish the WebSocket connection and authenticate. #### [disconnect](/docs/server-sdks/reference/python/relay/client/disconnect) Close the WebSocket connection cleanly. #### [run](/docs/server-sdks/reference/python/relay/client/run) Start the client with automatic reconnection. #### [dial](/docs/server-sdks/reference/python/relay/client/dial) Initiate an outbound call. #### [send\_message](/docs/server-sdks/reference/python/relay/client/send-message) Send an outbound SMS or MMS message. #### [receive](/docs/server-sdks/reference/python/relay/client/receive) Subscribe to additional contexts for inbound events. #### [unreceive](/docs/server-sdks/reference/python/relay/client/unreceive) Unsubscribe from inbound event contexts. #### [execute](/docs/server-sdks/reference/python/relay/client/execute) Send a raw JSON-RPC request to Relay. ## **Async Context Manager** `RelayClient` supports `async with` for scoped connections: ```python {4-5} import asyncio from signalwire.relay import RelayClient async def main(): async with RelayClient( project="your-project-id", token="your-api-token", contexts=["default"], ) as client: call = await client.dial( devices=[[{"type": "phone", "params": {"to_number": "+15559876543", "from_number": "+15551234567"}}]] ) # Automatically disconnects on exit asyncio.run(main()) ``` ## **Example** ```python {3} from signalwire.relay import RelayClient client = RelayClient( project="your-project-id", token="your-api-token", contexts=["default"], ) @client.on_call async def handle_call(call): await call.answer() action = await call.play([{"type": "tts", "params": {"text": "Hello from Relay!"}}]) await action.wait() await call.hangup() @client.on_message async def handle_message(message): print(f"SMS from {message.from_number}: {message.body}") client.run() ``` > WebSocket client for real-time call and message control. ## Docs - [connect](https://signalwire.com/docs/server-sdks/reference/python/relay/client/connect.md): Establish the WebSocket connection and authenticate. - [dial](https://signalwire.com/docs/server-sdks/reference/python/relay/client/dial.md): Initiate an outbound call. - [disconnect](https://signalwire.com/docs/server-sdks/reference/python/relay/client/disconnect.md): Close the WebSocket connection cleanly. - [execute](https://signalwire.com/docs/server-sdks/reference/python/relay/client/execute.md): Send a raw JSON-RPC request to Relay. - [receive](https://signalwire.com/docs/server-sdks/reference/python/relay/client/receive.md): Subscribe to additional contexts for inbound events. - [run](https://signalwire.com/docs/server-sdks/reference/python/relay/client/run.md): Start the client with automatic reconnection. - [send_message](https://signalwire.com/docs/server-sdks/reference/python/relay/client/send-message.md): Send an outbound SMS or MMS message. - [unreceive](https://signalwire.com/docs/server-sdks/reference/python/relay/client/unreceive.md): Unsubscribe from inbound event contexts.