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

# OpenClaw + WhatsApp

> Connect OpenClaw to WhatsApp in a Hyperbrowser sandbox

Run [OpenClaw](https://docs.openclaw.ai) in a Hyperbrowser sandbox and connect it
to WhatsApp. Your agent receives and responds to WhatsApp messages through the
built-in WhatsApp Web / Baileys channel.

## How It Works

1. Create a sandbox from the `openclaw` base image.
2. Configure OpenClaw with your model provider and the WhatsApp channel.
3. Link a WhatsApp account by scanning a QR code.
4. Start the OpenClaw gateway and message your agent.

## Prerequisites

* A [Hyperbrowser API key](https://app.hyperbrowser.ai)
* An OpenAI API key (or another [supported model provider](https://docs.openclaw.ai/start/openclaw))
* A WhatsApp account to link to the agent
* The Hyperbrowser [Node SDK](/sdks/node) or [Python SDK](/sdks/python)

## Quick Start

Create a sandbox, configure the WhatsApp channel with an allowlisted phone number,
and start the gateway:

<CodeGroup>
  ```typescript Node.js theme={null}
  import { Hyperbrowser } from "@hyperbrowser/sdk";
  import { config } from "dotenv";
  import { StringDecoder } from "node:string_decoder";

  config();

  const client = new Hyperbrowser({
    apiKey: process.env.HYPERBROWSER_API_KEY,
  });

  // 1. Create a sandbox from the openclaw image
  const sandbox = await client.sandboxes.create({
    imageName: "openclaw",
    timeoutMinutes: 60,
  });

  console.log("Sandbox ID:", sandbox.id);
  console.log("Session URL:", sandbox.sessionUrl);

  // 2. Set the default model
  await sandbox.exec(
    'openclaw config set agents.defaults.model.primary "openai/gpt-5.2"'
  );

  // 3. Install the WhatsApp plugin
  await sandbox.exec("openclaw plugins install @openclaw/whatsapp");

  const gatewayToken = process.env.OPENCLAW_APP_TOKEN ?? crypto.randomUUID();
  const gatewayPort = Number(process.env.GATEWAY_PORT ?? 18789);

  await sandbox.exec("openclaw config set gateway.mode local");

  // 4. Configure the WhatsApp channel (dedicated-number allowlist)
  const ownerNumber = "+15551234567"; // replace with the phone number allowed to DM the bot

  await sandbox.exec(
    [
      "openclaw config set channels.whatsapp.dmPolicy allowlist",
      `openclaw config set channels.whatsapp.allowFrom '["${ownerNumber}"]' --strict-json`,
      "openclaw config set channels.whatsapp.groupPolicy disabled",
    ].join(" && ")
  );

  // 5. Link WhatsApp in a PTY terminal so the QR renders correctly
  console.log("\nScan the QR code below with your WhatsApp app:\n");

  const login = await sandbox.terminal.create({
    command: "bash",
    args: ["-lc", "openclaw channels login --channel whatsapp"],
    rows: 40,
    cols: 120,
  });

  const loginConnection = await login.attach();
  const loginDecoder = new StringDecoder("utf8");

  for await (const event of loginConnection.events()) {
    if (event.type === "output") {
      process.stdout.write(loginDecoder.write(event.raw));
      continue;
    }

    break;
  }

  process.stdout.write(loginDecoder.end());
  await loginConnection.close();

  console.log("\nWhatsApp linked successfully.");

  // 6. Start the gateway
  const gateway = await sandbox.processes.start(
    `openclaw gateway --bind lan --port ${gatewayPort} --auth token --token "${gatewayToken}"`,
    { env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! } }
  );

  // 7. Wait for the gateway to become ready
  for (let i = 0; i < 15; i++) {
    const check = await sandbox.exec("openclaw gateway status --require-rpc");
    if (check.exitCode === 0) break;
    await new Promise((r) => setTimeout(r, 2000));
  }

  console.log("Gateway started. You can now message your agent on WhatsApp.");
  ```

  ```python Python theme={null}
  import codecs
  import os
  import time
  import uuid

  from dotenv import load_dotenv
  from hyperbrowser import Hyperbrowser
  from hyperbrowser.models import (
      CreateSandboxParams,
      SandboxTerminalCreateParams,
  )

  load_dotenv()

  client = Hyperbrowser(api_key=os.environ["HYPERBROWSER_API_KEY"])

  # 1. Create a sandbox from the openclaw image
  sandbox = client.sandboxes.create(
      CreateSandboxParams(
          image_name="openclaw",
          timeout_minutes=60,
      )
  )

  print(f"Sandbox ID: {sandbox.id}")
  print(f"Session URL: {sandbox.session_url}")

  # 2. Set the default model
  sandbox.exec('openclaw config set agents.defaults.model.primary "openai/gpt-5.2"')

  # 3. Install the WhatsApp plugin
  sandbox.exec("openclaw plugins install @openclaw/whatsapp")

  gateway_token = os.environ.get("OPENCLAW_APP_TOKEN", str(uuid.uuid4()))
  gateway_port = int(os.environ.get("GATEWAY_PORT", "18789"))

  sandbox.exec("openclaw config set gateway.mode local")

  # 4. Configure the WhatsApp channel (dedicated-number allowlist)
  owner_number = "+15551234567"  # replace with the phone number allowed to DM the bot
  allow_json = f'["{owner_number}"]'

  sandbox.exec(
      " && ".join([
          "openclaw config set channels.whatsapp.dmPolicy allowlist",
          f"openclaw config set channels.whatsapp.allowFrom '{allow_json}' --strict-json",
          "openclaw config set channels.whatsapp.groupPolicy disabled",
      ])
  )

  # 5. Link WhatsApp in a PTY terminal so the QR renders correctly
  print("\nScan the QR code below with your WhatsApp app:\n")

  login = sandbox.terminal.create(
      SandboxTerminalCreateParams(
          command="bash",
          args=["-lc", "openclaw channels login --channel whatsapp"],
          rows=40,
          cols=120,
      )
  )

  login_connection = login.attach()
  login_decoder = codecs.getincrementaldecoder("utf-8")()

  for event in login_connection.events():
      if event.type == "output":
          print(login_decoder.decode(event.raw), end="")
          continue

      break

  print(login_decoder.decode(b"", final=True), end="")
  login_connection.close()

  print("\nWhatsApp linked successfully.")

  # 6. Start the gateway
  gateway = sandbox.processes.start(
      f'openclaw gateway --bind lan --port {gateway_port} --auth token --token "{gateway_token}"',
      env={"OPENAI_API_KEY": os.environ["OPENAI_API_KEY"]},
  )

  # 7. Wait for the gateway to become ready
  for _ in range(15):
      check = sandbox.exec("openclaw gateway status --require-rpc")
      if check.exit_code == 0:
          break
      time.sleep(2)

  print("Gateway started. You can now message your agent on WhatsApp.")
  ```
</CodeGroup>

## Verify the Setup

After linking, verify everything is running from the sandbox terminal or via the SDK:

<CodeGroup>
  ```typescript Node.js theme={null}
  const status = await sandbox.exec("openclaw gateway status --require-rpc");
  console.log(status.stdout);

  const channels = await sandbox.exec("openclaw channels list");
  console.log(channels.stdout);

  const probe = await sandbox.exec("openclaw channels status --probe");
  console.log(probe.stdout);

  const deep = await sandbox.exec("openclaw status --deep");
  console.log(deep.stdout);
  ```

  ```python Python theme={null}
  status = sandbox.exec("openclaw gateway status --require-rpc")
  print(status.stdout)

  channels = sandbox.exec("openclaw channels list")
  print(channels.stdout)

  probe = sandbox.exec("openclaw channels status --probe")
  print(probe.stdout)

  deep = sandbox.exec("openclaw status --deep")
  print(deep.stdout)
  ```
</CodeGroup>

Send a WhatsApp message from the allowed phone number to the linked WhatsApp
number. With `dmPolicy=allowlist`, no pairing approval step is needed.

## Personal Number Setup

If you want to use your own WhatsApp number instead of a dedicated one, enable
`selfChatMode`. Replace step 4 in the quick start with:

<CodeGroup>
  ```typescript Node.js theme={null}
  const myNumber = "+15551234567"; // your personal WhatsApp number

  await sandbox.exec(
    [
      "openclaw config set channels.whatsapp.dmPolicy allowlist",
      `openclaw config set channels.whatsapp.allowFrom '["${myNumber}"]' --strict-json`,
      "openclaw config set channels.whatsapp.selfChatMode true --strict-json",
    ].join(" && ")
  );
  ```

  ```python Python theme={null}
  my_number = "+15551234567"  # your personal WhatsApp number
  allow_json = f'["{my_number}"]'

  sandbox.exec(
      " && ".join([
          "openclaw config set channels.whatsapp.dmPolicy allowlist",
          f"openclaw config set channels.whatsapp.allowFrom '{allow_json}' --strict-json",
          "openclaw config set channels.whatsapp.selfChatMode true --strict-json",
      ])
  )
  ```
</CodeGroup>

With `selfChatMode` enabled, send a message in your own WhatsApp chat
("Message yourself") and OpenClaw responds there.

## Adding Group Chats

To let your agent respond in group chats when mentioned, add this configuration
after the initial setup:

<CodeGroup>
  ```typescript Node.js theme={null}
  const ownerNumber = "+15551234567";

  await sandbox.exec(
    [
      "openclaw config set channels.whatsapp.groupPolicy allowlist",
      `openclaw config set channels.whatsapp.groupAllowFrom '["${ownerNumber}"]' --strict-json`,
      `openclaw config set channels.whatsapp.groups '{"*":{"requireMention":true}}' --strict-json`,
      "openclaw config validate",
    ].join(" && ")
  );
  ```

  ```python Python theme={null}
  owner_number = "+15551234567"
  group_allow_json = f'["{owner_number}"]'

  sandbox.exec(
      " && ".join([
          "openclaw config set channels.whatsapp.groupPolicy allowlist",
          f"openclaw config set channels.whatsapp.groupAllowFrom '{group_allow_json}' --strict-json",
          'openclaw config set channels.whatsapp.groups \'{"*":{"requireMention":true}}\' --strict-json',
          "openclaw config validate",
      ])
  )
  ```
</CodeGroup>

This means:

* Only allowlisted senders can control the agent in groups.
* The agent only responds when mentioned.

## Pairing Mode

If you want new DM senders to require explicit approval instead of an allowlist,
switch to pairing mode:

<CodeGroup>
  ```typescript Node.js theme={null}
  await sandbox.exec(
    [
      "openclaw config set channels.whatsapp.dmPolicy pairing",
      "openclaw config unset channels.whatsapp.allowFrom",
      "openclaw config validate",
    ].join(" && ")
  );

  // After an unknown sender messages the bot:
  const pairings = await sandbox.exec("openclaw pairing list whatsapp");
  console.log(pairings.stdout);

  // Approve a specific pairing code
  await sandbox.exec("openclaw pairing approve whatsapp CODE_HERE");
  ```

  ```python Python theme={null}
  sandbox.exec(
      " && ".join([
          "openclaw config set channels.whatsapp.dmPolicy pairing",
          "openclaw config unset channels.whatsapp.allowFrom",
          "openclaw config validate",
      ])
  )

  # After an unknown sender messages the bot:
  pairings = sandbox.exec("openclaw pairing list whatsapp")
  print(pairings.stdout)

  # Approve a specific pairing code
  sandbox.exec("openclaw pairing approve whatsapp CODE_HERE")
  ```
</CodeGroup>

<Tip>
  If you keep `allowFrom` populated alongside `dmPolicy=pairing`, allowlisted
  numbers bypass the pairing step.
</Tip>

## Troubleshooting

* **WhatsApp link lost after restart** -- The WhatsApp session lives in
  `~/.openclaw`. Use a [volume](/sandboxes/volumes) to persist it across sandbox
  restarts.
* **Messages not delivered** -- Verify the channel is connected with
  `openclaw channels status --probe`. Check that `allowFrom` includes the
  correct E.164 phone number (for example `+15551234567`).
* **QR code expired** -- Run `openclaw channels login --channel whatsapp` again
  to generate a new QR code.
* **Gateway bind errors** -- If you only need access from inside the sandbox
  (not from the host), set `gateway.bind` to `loopback` instead of `lan`.

## Next Steps

<CardGroup cols={2}>
  <Card title="Sandbox Processes" icon="play" href="/sandboxes/processes">
    Run and manage processes inside your sandbox.
  </Card>

  <Card title="Sandbox Snapshots" icon="camera" href="/sandboxes/snapshots">
    Save sandbox state and restore it later.
  </Card>

  <Card title="Volumes" icon="database" href="/sandboxes/volumes">
    Persist WhatsApp sessions and data across restarts.
  </Card>

  <Card title="Base Images" icon="box" href="/sandboxes/base-images">
    See all available base images.
  </Card>
</CardGroup>
