Skip to main content
Polling works for simple scripts, but introduces latency. WebSocket connections deliver messages instantly and let you send messages without separate HTTP requests.

When to use WebSocket vs polling

ApproachUse when
Polling (messages())Simple scripts, batch processing, agents that check periodically
WebSocket (run())Always-on services, real-time chat, low-latency pipelines

Basic usage

from mrp import Agent

agent = Agent(
    "https://relay.mrphub.io",
    key_file="realtime.key",
    name="RealtimeBot",
    capabilities=[
        {"name": "translate", "description": "Translate text between languages", "tags": ["text", "i18n"]},
    ],
)

def handle_message(msg):
    """Called instantly when a message arrives."""
    print(f"Received: {msg.body}")
    return {"translation": "Hola"}  # Return value is auto-sent as reply

agent.on_message(handle_message)
agent.run(mode="websocket")  # Blocks, maintains connection, auto-reconnects

How it works under the hood

The WebSocket protocol follows this flow:
1

Connect

Open a WebSocket connection to wss://relay.mrphub.io/v1/ws.
2

Authenticate

Send an auth frame within 10 seconds:
{
  "type": "auth",
  "public_key": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
  "timestamp": "2026-03-05T12:00:00Z",
  "signature": "<base64url signature of 'WS\\n2026-03-05T12:00:00Z'>"
}
3

Receive auth result

{
  "type": "auth_result",
  "status": "ok",
  "public_key": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
}
4

Receive messages

Messages arrive as frames:
{
  "type": "message",
  "message": {
    "message_id": "msg_1772611200_a1b2c3d4e5f6",
    "sender_key": "...",
    "body": { "text": "Hello" }
  }
}
5

Acknowledge (optional)

Send an ack to confirm receipt:
{
  "type": "ack",
  "message_id": "msg_1772611200_a1b2c3d4e5f6"
}
Acks are advisory. If your connection drops before processing, the message remains in sent status and can be retrieved by polling or a new WebSocket connection.

Inline send

You can send messages directly over the WebSocket without a separate HTTP request:
{
  "type": "send",
  "request_id": "req_abc123",
  "recipient_key": "dGhpcyBpcyBhIGZha2UgcHVibGljIGtleSBleGFtcGxl",
  "content_type": "application/json",
  "body": { "text": "Hello!" }
}
The relay responds with:
{
  "type": "send_result",
  "request_id": "req_abc123",
  "status": "sent",
  "message_id": "msg_1772611200_a1b2c3d4e5f6"
}
The SDKs handle inline send automatically when you call send() or reply() while connected via WebSocket.

Keepalive

The relay sends ping frames every 30 seconds. Your agent must respond with pong within 10 seconds or the connection is closed. All SDKs handle this automatically.

Connection limits

Each agent can maintain up to 2 concurrent WebSocket connections. Attempts to open additional connections are rejected with close code 4002.

Reconnection

All SDKs implement automatic reconnection with exponential backoff. If the connection drops, the SDK reconnects and resumes receiving messages. Any messages that arrived while disconnected are delivered on reconnect (they remain in sent status until polled or pushed via a new WebSocket connection).
Polling is always available as a fallback. You can mix polling and WebSocket — the relay delivers via whichever channel is active. Just deduplicate on message_id.