OpenClaw Integration

OpenClaw is the AI that actually does things -clearing inboxes, sending emails, managing calendars, checking you in for flights. ClawXP is the real-time transport layer that powers OpenClaw's live updates.

This guide shows you how to subscribe to OpenClaw agent events, stream live reasoning steps, and build reactive UIs that update as OpenClaw works.

ℹ️

ClawXP + OpenClaw -ClawXP is the official pub/sub backend for OpenClaw's live agent streams. All real-time events from OpenClaw agents flow through a ClawXP server.

Prerequisites

  • @clawxp/sdk installed (npm install @clawxp/sdk)
  • A ClawXP server running (see Server docs) or access to an OpenClaw-hosted endpoint
  • An OpenClaw API key from openclaw.ai

Channel conventions

OpenClaw uses the following channel naming conventions:

| Channel pattern | Mode | Description | |---|---|---| | openclaw:agent-stream | stream | Live agent reasoning steps | | openclaw:tool-calls | stream | Tool execution events (browse, email, calendar) | | openclaw:agent-done | stream | Task completion event with summary | | openclaw:notifications | stream | Proactive agent check-in notifications | | openclaw:status | datagram | Agent status heartbeat (high frequency) |

Basic setup

import { ClawXPClient } from '@clawxp/sdk';

const client = new ClawXPClient({
  url: 'wss://your-clawxp-server.com',
  token: process.env.CLAWXP_TOKEN,
});

// Subscribe to live agent reasoning
await client.subscribe('openclaw:agent-stream', {
  mode: 'stream',
  onMessage: (msg) => {
    const { thought, step, agentId } = msg.data;
    console.log(`[${agentId}] Step ${step}: ${thought}`);
  },
});

Streaming agent thoughts to a React UI

import { useState, useEffect } from 'react';
import { ClawXPClient } from '@clawxp/sdk';

export function AgentThoughts() {
  const [thoughts, setThoughts] = useState<string[]>([]);

  useEffect(() => {
    const client = new ClawXPClient({
      url: process.env.NEXT_PUBLIC_CLAWXP_URL!,
      token: process.env.NEXT_PUBLIC_CLAWXP_TOKEN!,
    });

    let unsub: (() => void) | undefined;

    client.subscribe('openclaw:agent-stream', {
      mode: 'stream',
      onMessage: (msg) => {
        setThoughts((prev) => [...prev, msg.data.thought]);
      },
    }).then((sub) => {
      unsub = sub.unsubscribe;
    });

    return () => unsub?.();
  }, []);

  return (
    <ul>
      {thoughts.map((t, i) => <li key={i}>{t}</li>)}
    </ul>
  );
}

Listening for tool calls

When OpenClaw performs an action (browsing the web, sending an email), it publishes a tool-call event:

await client.subscribe('openclaw:tool-calls', {
  mode: 'stream',
  onMessage: (msg) => {
    const { tool, input, status, output } = msg.data;

    switch (tool) {
      case 'browse':
        console.log(`Browsing: ${input.url}`);
        break;
      case 'send_email':
        console.log(`Email sent to: ${input.to}`);
        break;
      case 'create_event':
        console.log(`Calendar event created: ${input.title}`);
        break;
    }
  },
});

Task completion events

When an OpenClaw task finishes, subscribe to openclaw:agent-done for the final summary:

await client.subscribe('openclaw:agent-done', {
  mode: 'stream',
  onMessage: (msg) => {
    const { taskId, summary, duration, toolsUsed } = msg.data;
    console.log(`Task ${taskId} done in ${duration}ms`);
    console.log('Summary:', summary);
  },
});

Agent status heartbeat (datagram mode)

For a live "thinking / idle" indicator, use datagram mode on the status channel:

await client.subscribe('openclaw:status', {
  mode: 'datagram', // Drop stale status packets -freshness matters
  onMessage: (msg) => {
    const { agentId, state } = msg.data;
    updateStatusIndicator(agentId, state); // 'thinking' | 'idle' | 'waiting'
  },
});

Publishing to OpenClaw

You can publish tasks or instructions directly to an OpenClaw agent via ClawXP:

// Trigger an OpenClaw task
await client.publish('openclaw:tasks', {
  instruction: 'Check my inbox and summarize unread emails from today',
  userId: 'u_42',
  priority: 'normal',
});
⚠️

Publishing to OpenClaw channels requires appropriate authorization claims in your JWT. Ensure your token includes "publish:openclaw" in its permissions scope.

Full example: OpenClaw live dashboard

import { ClawXPClient } from '@clawxp/sdk';

const client = new ClawXPClient({
  url: process.env.CLAWXP_URL!,
  token: process.env.CLAWXP_TOKEN!,
});

// Subscribe to all relevant OpenClaw channels
await Promise.all([
  client.subscribe('openclaw:agent-stream', {
    mode: 'stream',
    onMessage: (msg) => ui.appendThought(msg.data),
  }),
  client.subscribe('openclaw:tool-calls', {
    mode: 'stream',
    onMessage: (msg) => ui.showToolCall(msg.data),
  }),
  client.subscribe('openclaw:agent-done', {
    mode: 'stream',
    onMessage: (msg) => ui.showSummary(msg.data),
  }),
  client.subscribe('openclaw:status', {
    mode: 'datagram',
    onMessage: (msg) => ui.updateStatus(msg.data.state),
    onStale: (count) => console.log(`Dropped ${count} stale status ticks`),
  }),
]);

Troubleshooting

I'm not receiving any messages

  • Verify your JWT token has the correct claims for the OpenClaw channels
  • Ensure your ClawXP server is configured with the OpenClaw adapter
  • Check the Playground to confirm your ClawXP server is reachable

Messages arrive out of order

  • For agent thoughts and tool calls, always use mode: 'stream' -this guarantees ordered delivery
  • Only use mode: 'datagram' for status heartbeats where freshness beats completeness

Agent stream stops mid-task

  • The ClawXP SDK automatically reconnects and resubscribes on any network interruption
  • Your onMessage handler will resume receiving events once connectivity is restored

For questions about OpenClaw itself, visit openclaw.ai or the OpenClaw Discord.