> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipecat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Messaging

> Send and receive arbitrary messages between your client and Pipecat bot.

<View title="React" icon="react">
  <Callout icon="react" color="#FFC107">
    You are currently viewing the React version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="JavaScript" icon="js">
  <Callout icon="js" color="#FFC107">
    You are currently viewing the JavaScript version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="React Native" icon="mobile">
  <Callout icon="mobile" color="#FFC107">
    You are currently viewing the React Native version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="iOS" icon="apple">
  <Callout icon="apple" color="#FFC107">
    You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

The Pipecat client can send and receive arbitrary messages to and from the server running the bot. This is useful for passing configuration at startup, triggering actions mid-session, querying server state, and receiving notifications from the bot.

## Connection-time configuration

Often you need to pass configuration to the server when starting the bot — a system prompt, preferred language, user preferences, or any other data the server needs before the pipeline runs. Pass it via `requestData` in `startBotAndConnect()`.

<View title="React" icon="react">
  ```tsx theme={null}
  import { usePipecatClient } from "@pipecat-ai/client-react";

  function MyComponent() {
    const client = usePipecatClient();

    const handleStart = async () => {
      try {
        await client.startBotAndConnect({
          endpoint: "/api/start",
          requestData: {
            initial_prompt: "You are a pirate captain",
            preferred_language: "en-US",
          },
        });
      } catch (error) {
        console.error("Error starting the bot:", error);
      }
    };
  }
  ```
</View>

<View title="JavaScript" icon="js">
  ```ts theme={null}
  try {
    await client.startBotAndConnect({
      endpoint: "/api/start",
      requestData: {
        initial_prompt: "You are a pirate captain",
        preferred_language: "en-US",
      },
    });
  } catch (error) {
    console.error("Error starting the bot:", error);
  }
  ```
</View>

<View title="React Native" icon="mobile">
  ```tsx theme={null}
  try {
    await client.startBotAndConnect({
      endpoint: "/api/start",
      requestData: {
        initial_prompt: "You are a pirate captain",
        preferred_language: "en-US",
      },
    });
  } catch (error) {
    console.error("Error starting the bot:", error);
  }
  ```
</View>

<View title="iOS" icon="apple">
  Pass data via the `requestData` field of `APIRequest`:

  ```swift theme={null}
  let startParams = APIRequest.init(
    endpoint: URL(string: "https://your-server.com/api/start")!,
    requestData: .object([
      "initial_prompt": .string("You are a pirate captain"),
      "preferred_language": .string("en-US"),
    ])
  )

  client.startBotAndConnect(startBotParams: startParams) { (result: Result<SmallWebRTCStartBotResult, AsyncExecutionError>) in
    switch result {
    case .failure(let error):
      print("Error starting the bot: \(error)")
    case .success:
      break
    }
  }
  ```
</View>

On the server, your endpoint receives this data as the request body and can forward relevant values to the bot process or use them to configure the pipeline:

<CodeGroup>
  ```python FastAPI endpoint theme={null}
  @app.post("/api/start")
  async def start(request: Request) -> Dict[Any, Any]:
      body = await request.json()
      prompt = body.get("initial_prompt", "You are a pirate captain")
      lang = body.get("preferred_language", "en-US")

      room_url, token = await create_room_and_token()

      proc = subprocess.Popen(
          [f"python3 -m bot -u {room_url} -t {token} -p {prompt} -l {lang}"],
          shell=True,
          cwd=os.path.dirname(os.path.abspath(__file__)),
      )
      bot_procs[proc.pid] = (proc, room_url)

      return {"url": room_url, "token": token}
  ```

  ```python bot theme={null}
  def extract_arguments():
      parser = argparse.ArgumentParser()
      parser.add_argument("-u", "--room-url", type=str)
      parser.add_argument("-t", "--token", type=str)
      parser.add_argument("-p", "--prompt", type=str, default="You are a pirate captain")
      parser.add_argument("-l", "--language", type=str, default="en-US")
      return parser.parse_args()

  async def main():
      args = extract_arguments()

      llm = GeminiLiveLLMService(
          api_key=os.getenv("GOOGLE_API_KEY"),
          settings=GeminiLiveLLMService.Settings(
              system_instruction=args.prompt,
              language=args.language,
          ),
      )
  ```
</CodeGroup>

***

## Sending messages to the server

Use `sendClientMessage()` to send a fire-and-forget message to the bot. The server handles it and is not expected to send a direct response.

<View title="React" icon="react">
  ```tsx theme={null}
  const client = usePipecatClient();

  client.sendClientMessage("set-language", { language: "en-US" });
  ```
</View>

<View title="JavaScript" icon="js">
  ```ts theme={null}
  client.sendClientMessage("set-language", { language: "en-US" });
  ```
</View>

<View title="React Native" icon="mobile">
  ```tsx theme={null}
  client.sendClientMessage("set-language", { language: "en-US" });
  ```
</View>

<View title="iOS" icon="apple">
  ```swift theme={null}
  try client.sendClientMessage(
    msgType: "set-language",
    data: .object(["language": .string("en-US")])
  )
  ```
</View>

On the server, handle it via the `on_client_message` event handler or from inside a `FrameProcessor`:

<CodeGroup>
  ```python event handler theme={null}
  @rtvi.event_handler("on_client_message")
  async def on_client_message(rtvi, msg):
      if msg.type == "set-language":
          language = msg.data.get("language", "en-US")
          await task.queue_frames([STTUpdateSettingsFrame(language=language)])
  ```

  ```python FrameProcessor theme={null}
  class CustomFrameProcessor(FrameProcessor):
      async def process_frame(self, frame: Frame, direction: FrameDirection):
          await super().process_frame(frame, direction)
          if isinstance(frame, RTVIClientMessageFrame):
              if frame.type == "set-language":
                  language = frame.data.get("language", "en-US")
                  await self.push_frame(STTUpdateSettingsFrame(language=language))
                  return
          await self.push_frame(frame, direction)
  ```
</CodeGroup>

***

## Requesting data from the server

Use `sendClientRequest()` to send a message and wait for a response. Useful for querying server state or triggering an action that needs to confirm success or failure.

<View title="React" icon="react">
  ```tsx theme={null}
  const client = usePipecatClient();

  try {
    const response = await client.sendClientRequest("get-language", {});
    console.log("Current language:", response.language);
  } catch (error) {
    console.error("Request failed:", error);
  }
  ```
</View>

<View title="JavaScript" icon="js">
  ```ts theme={null}
  try {
    const response = await client.sendClientRequest("get-language", {});
    console.log("Current language:", response.language);
  } catch (error) {
    console.error("Request failed:", error);
  }
  ```
</View>

<View title="React Native" icon="mobile">
  ```tsx theme={null}
  try {
    const response = await client.sendClientRequest("get-language", {});
    console.log("Current language:", response.language);
  } catch (error) {
    console.error("Request failed:", error);
  }
  ```
</View>

<View title="iOS" icon="apple">
  ```swift theme={null}
  Task {
    do {
      let response = try await client.sendClientRequest(msgType: "get-language")
      if let language = response.d?.asObject?["language"]?.asString {
        print("Current language: \(language)")
      }
    } catch {
      print("Request failed: \(error)")
    }
  }
  ```
</View>

On the server, respond with `send_server_response()` or push a `RTVIServerResponseFrame`:

<CodeGroup>
  ```python event handler theme={null}
  @rtvi.event_handler("on_client_message")
  async def on_client_message(rtvi, msg):
      if msg.type == "get-language":
          await rtvi.send_server_response(msg, {"language": get_current_language()})
      else:
          await rtvi.send_error_response(msg, "Unknown request type")
  ```

  ```python FrameProcessor theme={null}
  class CustomFrameProcessor(FrameProcessor):
      async def process_frame(self, frame: Frame, direction: FrameDirection):
          await super().process_frame(frame, direction)
          if isinstance(frame, RTVIClientMessageFrame):
              if frame.type == "get-language":
                  await self.push_frame(
                      RTVIServerResponseFrame(
                          client_msg=frame,
                          data={"language": get_current_language()},
                      )
                  )
                  return
              else:
                  await self.push_frame(
                      RTVIServerResponseFrame(
                          client_msg=frame,
                          error="Unknown request type",
                      )
                  )
          await self.push_frame(frame, direction)
  ```
</CodeGroup>

***

## Receiving messages from the server

The server can push unsolicited messages to the client at any time — for example, to notify the client that a setting has changed.

<View title="React" icon="react">
  Use `useRTVIClientEvent` to subscribe to `ServerMessage` within a component:

  ```tsx theme={null}
  import { RTVIEvent, RTVIMessage } from "@pipecat-ai/client-js";
  import { useRTVIClientEvent } from "@pipecat-ai/client-react";

  type LanguageData = {
    msg: string;
    language: string;
  };

  function LanguageListener() {
    useRTVIClientEvent(
      RTVIEvent.ServerMessage,
      useCallback((message: RTVIMessage) => {
        const data: LanguageData = message.data as LanguageData;
        if (data.msg === "language-updated") {
          console.log("Language updated to:", data.language);
        }
      }, [])
    );
  }
  ```
</View>

<View title="JavaScript" icon="js">
  Listen for the `ServerMessage` event on the client, or pass `onServerMessage` as a constructor callback:

  ```ts theme={null}
  client.on(RTVIEvent.ServerMessage, (message) => {
    if (message.data.msg === "language-updated") {
      console.log("Language updated to:", message.data.language);
    }
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Listen for the `ServerMessage` event using `useEffect` with cleanup:

  ```tsx theme={null}
  import { RTVIEvent } from "@pipecat-ai/client-js";
  import { useEffect } from "react";

  function LanguageListener() {
    useEffect(() => {
      const handler = (message) => {
        if (message.data.msg === "language-updated") {
          console.log("Language updated to:", message.data.language);
        }
      };
      client.on(RTVIEvent.ServerMessage, handler);
      return () => client.off(RTVIEvent.ServerMessage, handler);
    }, []);
  }
  ```
</View>

<View title="iOS" icon="apple">
  Implement `onServerMessage` in your `PipecatClientDelegate` to receive unsolicited server messages. The SDK decodes the message payload and passes it as a `Value`:

  ```swift theme={null}
  func onServerMessage(data: Any) {
    Task { @MainActor in
      guard let value = data as? Value else { return }
      let obj = value.asObject
      guard obj["msg"]?.asString == "language-updated",
            let language = obj["language"]?.asString else { return }
      print("Language updated to: \(language)")
    }
  }
  ```
</View>

On the server, send messages via `send_server_message()` or by pushing a `RTVIServerMessageFrame`:

<CodeGroup>
  ```python Observer theme={null}
  class CustomObserver(BaseObserver):
      async def on_push_frame(self, data: FramePushed):
          if isinstance(data.frame, STTUpdateSettingsFrame):
              for key, value in data.frame.settings.items():
                  if key == "language":
                      await rtvi.send_server_message({
                          "msg": "language-updated",
                          "language": value,
                      })
  ```

  ```python FrameProcessor theme={null}
  class CustomFrameProcessor(FrameProcessor):
      async def process_frame(self, frame: Frame, direction: FrameDirection):
          await super().process_frame(frame, direction)
          if isinstance(frame, STTUpdateSettingsFrame):
              for key, value in frame.settings.items():
                  if key == "language":
                      await self.push_frame(
                          RTVIServerMessageFrame(
                              data={"msg": "language-updated", "language": value}
                          )
                      )
          await self.push_frame(frame, direction)
  ```
</CodeGroup>

***

## API reference

<CardGroup cols={2}>
  <Card title="Client Methods" icon="js" href="/api-reference/client/js/client-methods#messages">
    `sendClientMessage`, `sendClientRequest`, `registerFunctionCallHandler`
  </Card>

  <Card title="Callbacks & Events" icon="bell" href="/api-reference/client/js/callbacks#messages-and-errors">
    `onServerMessage`, `onMessageError`, and related events
  </Card>
</CardGroup>
