DocsOpenClaw CLIHub Channel Plugin

Hub Channel Plugin

The clawployees-hub channel plugin is an OpenClaw channel extension that connects your agent to the Clawployees dashboard for hub messaging. It is installed automatically during provisioning and is verified working in production — connected agents respond to dashboard chat messages in real time.

Overview

Unlike external platforms (Slack, Telegram) that push messages to agents via webhooks, the Clawployees hub uses a polling model. The agent's plugin polls the hub API every 2 seconds for new user messages, processes them through the OpenClaw agent pipeline, and posts responses back. This means the agent works behind firewalls and NATs without needing a public IP or open ports.

Plugin Structure

The plugin lives at ~/.openclaw/extensions/clawployees-hub/ and contains three files:

FilePurpose
`index.ts`Main plugin code — registration, gateway polling, outbound delivery
`openclaw.plugin.json`Plugin manifest declaring the `clawployees` channel
`package.json`Package metadata with `openclaw.channel` and `openclaw.install` hints

How It Follows the OpenClaw Plugin API

The plugin follows the standard OpenClaw channel plugin pattern used by all official channels (Telegram, DingTalk, Nostr, etc.):

**Entry point** — Default export is a plugin object with id, name, configSchema, and register(api). The plugin id must match the manifest id ("clawployees-hub"), while the channel ID is "clawployees":

const plugin = {
  id: "clawployees-hub",  // matches openclaw.plugin.json id
  name: "Clawployees Hub Channel",
  configSchema: emptyPluginConfigSchema(),
  register(api: OpenClawPluginApi): void {
    setRuntime(api.runtime);
    api.registerChannel({ plugin: clawployeesPlugin }); // registers channel id "clawployees"
  },
};
export default plugin;

**Runtime singleton** — The PluginRuntime is stored from api.runtime during register() and retrieved later in startAccount(). This is the standard pattern — external plugins must use api.runtime (PluginRuntime), not ctx.runtime (RuntimeEnv).

Channel plugin object — Implements the standard adapters:

AdapterPurpose
`meta`Channel ID, label, aliases
`capabilities`Declares `chatTypes: ["direct"]`
`config.listAccountIds`Lists account IDs from `cfg.channels.clawployees.accounts`
`config.resolveAccount`Resolves account config by ID (spreads full config including `hub_url`, `connection_token`)
`outbound.sendText`Posts agent response to hub API
`gateway.startAccount`Starts the polling loop for inbound messages

Inbound Message Pipeline

When the plugin receives a new user message, it dispatches through the standard OpenClaw inbound pipeline:

1. rt.channel.routing.resolveAgentRoute()     — determine which agent handles it
2. rt.channel.reply.formatInboundEnvelope()    — format the message envelope
3. rt.channel.reply.finalizeInboundContext()   — build typed context payload
4. rt.channel.session.recordInboundSession()   — persist session metadata
5. rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher() — run agent, deliver reply

The deliver callback inside the dispatcher posts each response block back to the hub via POST /api/connect/messages.

Outbound Delivery

The sendText handler receives { cfg, to, text, accountId, log } (standard OpenClaw outbound params). The to field contains the conversation ID (set as From/To in the inbound context). It posts to:

POST {hub_url}/messages
Authorization: Bearer {connection_token}
{ "conversation_id": <to>, "content": <text> }

Gateway Lifecycle

The gateway.startAccount function:

  1. Starts a polling loop that runs every poll_interval ms (default: 2000)
  2. Polls GET /api/connect/conversations to discover active conversations with pending user messages
  3. For each conversation with new messages, fetches them via GET /api/connect/messages/{id}?after={lastSeen}
  4. Dispatches each message through the inbound pipeline
  5. Parks the returned promise on the abortSignal (keeps the account "running")
  6. On abort, clears the polling timer and resolves

Configuration

The plugin reads its config from channels.clawployees in openclaw.json:

{
  "channels": {
    "clawployees": {
      "accounts": {
        "default": {
          "hub_url": "https://clawployees.com/api/connect",
          "connection_token": "your-64-char-token"
        }
      }
    }
  }
}

This config is written automatically during provisioning. The hub_url and connection_token are provided in the provisioning payload.

Plugin Manifest

openclaw.plugin.json:

{
  "id": "clawployees-hub",
  "channels": ["clawployees"],
  "configSchema": {
    "type": "object",
    "additionalProperties": true,
    "properties": {}
  }
}

The channels array declares which channel IDs this plugin provides. OpenClaw uses this to know that channels.clawployees in the config should be routed to this plugin.

Package Metadata

package.json includes an openclaw block:

{
  "openclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "clawployees",
      "label": "Clawployees Hub",
      "aliases": ["hub"]
    },
    "install": {
      "localPath": "extensions/clawployees-hub",
      "defaultChoice": "local"
    }
  }
}

Hub API Endpoints Used

EndpointMethodPurpose
`/api/connect/conversations`GETDiscover active conversations with pending messages
`/api/connect/messages/{conversation}`GETFetch new user messages (supports `?after={id}` for pagination)
`/api/connect/messages`POSTPost agent response to a conversation
`/api/connect/register`POSTRegister agent with the hub (during provisioning)
`/api/connect/heartbeat`POSTPeriodic keep-alive signal

All endpoints require Authorization: Bearer {connection_token} header.

Key Design Decisions

Polling vs webhooks — The hub can't push to agents because agents typically bind to loopback (127.0.0.1) and sit behind firewalls. Polling at 2-second intervals provides near-real-time messaging without requiring public IPs or port forwarding. This is verified working in production with connected agents responding to dashboard chat.

Channel plugin vs skill — Using the OpenClaw channel plugin API (not a custom skill) means messages flow through the standard agent pipeline with proper routing, session management, and dispatch. This is the same architecture used by Telegram, Slack, DingTalk, and all other OpenClaw channels.

**Conversation as peer ID** — The dashboard conversation ID is used as both the From and To fields in the OpenClaw context. This maps each conversation to an isolated session, matching how other channels map chat IDs to sessions.

**Plugin ID vs Channel ID** — The plugin id ("clawployees-hub") must match the manifest id in openclaw.plugin.json. The channel id ("clawployees") is separate and used in the config path channels.clawployees and the channel plugin object. Mismatching these causes OpenClaw to log a warning and may prevent the gateway from starting.

Reference Implementations

This plugin follows the same patterns used by:

  • **DingTalk channel** — @soimy/dingtalk (gateway + dispatch pipeline)
  • **OpenClawCity channel** — @openclawcity/openclawcity (runtime singleton, WebSocket inbound)
  • **Nostr NIP-29 channel** — k0sti/openclaw-nostr (dispatch pipeline documentation)

See the [OpenClaw plugin docs](https://docs.openclaw.ai/tools/plugin) for the full channel plugin API reference.