***

title: RelayClient
slug: /reference/python/relay/client
description: WebSocket client for real-time call and message control.
max-toc-depth: 3
---------------------

For a complete index of all SignalWire documentation pages, fetch https://signalwire.com/docs/llms.txt

[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**

<ParamField path="project" type="str" toc={true}>
  SignalWire project ID. Set via constructor or `SIGNALWIRE_PROJECT_ID` environment variable.
</ParamField>

<ParamField path="token" type="str" toc={true}>
  API token for authentication. Set via constructor or `SIGNALWIRE_API_TOKEN` environment variable.
</ParamField>

<ParamField path="jwt_token" type="str" toc={true}>
  JWT token for alternative authentication. Set via constructor or `SIGNALWIRE_JWT_TOKEN` environment variable.
  When provided, `project` and `token` are not required.
</ParamField>

<ParamField path="host" type="str" default="relay.signalwire.com" toc={true}>
  SignalWire space hostname (e.g., `your-space.signalwire.com`). Set via constructor or `SIGNALWIRE_SPACE`
  environment variable. Defaults to `relay.signalwire.com`.
</ParamField>

<ParamField path="contexts" type="list[str]" default="[]" toc={true}>
  List of contexts to subscribe to for inbound call and message events.
</ParamField>

<ParamField path="max_active_calls" type="Optional[int]" default="1000" toc={true}>
  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.
</ParamField>

<ParamField path="relay_protocol" type="str" toc={true}>
  Server-assigned protocol string from the connect response. Read-only. Used internally
  for session resumption on reconnect.
</ParamField>

## **Decorators**

### on\_call

```python {11-12}
from signalwire.relay import RelayClient
from signalwire.relay.call import Call

client = RelayClient(
    project="your-project-id",
    token="your-api-token",
    host="your-space.signalwire.com",
    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 {11-12}
from signalwire.relay import RelayClient
from signalwire.relay.message import Message

client = RelayClient(
    project="your-project-id",
    token="your-api-token",
    host="your-space.signalwire.com",
    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**

<CardGroup cols={3}>
  <Card title="connect" href="/docs/server-sdks/reference/python/relay/client/connect">
    Establish the WebSocket connection and authenticate.
  </Card>

  <Card title="disconnect" href="/docs/server-sdks/reference/python/relay/client/disconnect">
    Close the WebSocket connection cleanly.
  </Card>

  <Card title="run" href="/docs/server-sdks/reference/python/relay/client/run">
    Start the client with automatic reconnection.
  </Card>

  <Card title="dial" href="/docs/server-sdks/reference/python/relay/client/dial">
    Initiate an outbound call.
  </Card>

  <Card title="send_message" href="/docs/server-sdks/reference/python/relay/client/send-message">
    Send an outbound SMS or MMS message.
  </Card>

  <Card title="receive" href="/docs/server-sdks/reference/python/relay/client/receive">
    Subscribe to additional contexts for inbound events.
  </Card>

  <Card title="unreceive" href="/docs/server-sdks/reference/python/relay/client/unreceive">
    Unsubscribe from inbound event contexts.
  </Card>

  <Card title="execute" href="/docs/server-sdks/reference/python/relay/client/execute">
    Send a raw JSON-RPC request to RELAY.
  </Card>
</CardGroup>

## **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",
        host="your-space.signalwire.com",
        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",
    host="your-space.signalwire.com",
    contexts=["default"],
)

@client.on_call
async def handle_call(call):
    await call.answer()
    action = await call.play([{"type": "tts", "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()
```