Claw Messenger

API Documentation

Connect your AI agent to iMessage, RCS & SMS via WebSocket.

Claw Messenger is a relay that connects your AI agent to iMessage, RCS, and SMS. Your agent connects via WebSocket and sends/receives messages in real time. Phone numbers are provisioned through our partner network — you register them in the dashboard, and we handle the carrier integration.

Quickstart

  1. Sign up at clawmessenger.com and start a free trial
  2. Register a phone number during onboarding
  3. Copy your API key from the dashboard
  4. Connect via WebSocket:
const ws = new WebSocket("wss://claw-messenger.onrender.com/ws?key=YOUR_API_KEY");

ws.onopen = () => {
  console.log("Connected");
  // Send a message
  ws.send(JSON.stringify({
    type: "send",
    id: "msg-1",
    to: "+1234567890",
    parts: [{ type: "text", value: "Hello from my agent!" }],
    service: "iMessage"
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("Received:", data);
};

Using OpenClaw? Install the plugin instead: openclaw plugins install @emotion-machine/claw-messenger. See the setup guide.

How Phone Numbers Work

You register the phone numbers that should be able to communicate with your agent. When a registered number texts the agent, the relay routes the message to your account via WebSocket. Unregistered numbers are ignored.

Register a number programmatically

curl -X POST https://claw-messenger.onrender.com/api/routes \
  -H "Authorization: Bearer cm_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+1234567890"}'

# Response:
# {"ok": true, "phone_number": "+1234567890", "already_claimed": false}

WebSocket Connection

Endpoint

wss://claw-messenger.onrender.com/ws?key=YOUR_API_KEY

The API key is passed as a key query parameter. Generate keys in the dashboard or via the REST API.

Connection lifecycle

  1. Connect to the WebSocket endpoint with your API key
  2. Server validates the key and accepts the connection
  3. Exchange ping/pong every 30 seconds to stay alive
  4. Send and receive JSON messages
  5. On disconnect, reconnect with exponential backoff (max 30s)

Limits

Ping / Pong

Send {"type": "ping"} to keep the connection alive. The server responds with {"type": "pong"}. The server also sends pings — respond with pong to avoid disconnection.

Sending Messages

// Client -> Server
{
  "type": "send",
  "id": "msg-1",
  "to": "+1234567890",
  "parts": [{ "type": "text", "value": "Hello!" }],
  "service": "iMessage"
}

// Server -> Client (response)
{
  "type": "send.result",
  "id": "msg-1",
  "ok": true,
  "messageId": "abc123",
  "chatId": "chat-456"
}
FieldTypeRequiredDescription
typestringYes"send"
idstringYesCorrelation ID — returned in send.result
tostring | string[]Yes*E.164 phone number(s). Use chatId instead for existing groups.
chatIdstringYes*Send to an existing group. Use instead of to.
partsarrayYesMessage parts. Each: { "type": "text", "value": "..." }
servicestringNo"iMessage" (default), "SMS", or "RCS"

* Provide either to or chatId, not both.

Receiving Messages

Inbound messages arrive as message events:

// Server -> Client
{
  "type": "message",
  "messageId": "abc123",
  "chatId": "chat-456",
  "from": "+1234567890",
  "text": "Hey, got your message!",
  "attachments": [],
  "service": "iMessage",
  "isGroup": false,
  "participants": []
}

attachments is an array of { url, mimeType } objects for media messages (images, files).

Typing & Read Receipts

Send typing indicator

// Start typing
{ "type": "typing.start", "to": "+1234567890" }

// Stop typing
{ "type": "typing.stop", "to": "+1234567890" }

Mark as read

{ "type": "read", "to": "+1234567890" }

Receive typing indicator

// Server -> Client
{
  "type": "typing",
  "from": "+1234567890",
  "started": true
}

Reactions

Send a reaction

{
  "type": "reaction",
  "messageId": "abc123",
  "reactionType": "love",
  "remove": false
}

Reaction types: love, like, dislike, laugh, emphasize, question. Set remove: true to remove a reaction.

Receive a reaction

// Server -> Client
{
  "type": "reaction",
  "messageId": "abc123",
  "from": "+1234567890",
  "reactionType": "love",
  "added": true
}

Group Messages

To create a new group, send to multiple phone numbers:

{
  "type": "send",
  "id": "grp-1",
  "to": ["+1234567890", "+0987654321"],
  "parts": [{ "type": "text", "value": "Hello group!" }],
  "service": "iMessage"
}

To send to an existing group, use chatId from a previous send.result or inbound message:

{
  "type": "send",
  "id": "grp-2",
  "chatId": "chat-456",
  "parts": [{ "type": "text", "value": "Follow-up" }]
}

Inbound group messages have isGroup: true and include a participants array.

Message Sync

After reconnecting, replay missed messages with a sync request:

// Client -> Server
{ "type": "sync", "since": "2026-04-10T14:30:00Z" }

// Server replays individual "message" events with replay: true
// Then sends:
{ "type": "sync.done", "count": 5 }

Delivery Status

// Server -> Client
{
  "type": "status",
  "messageId": "abc123",
  "status": "delivered"
}

Status values: delivered, read, failed.

REST Endpoints

Most REST endpoints require a Clerk JWT in the Authorization: Bearer header (from your dashboard session). Phone number endpoints also accept your API key directly — pass Authorization: Bearer cm_live_... to manage numbers programmatically from your agent or scripts.

API Keys

MethodPathDescription
POST/api/keysCreate a new API key (raw key returned once)
GET/api/keysList API keys (prefix + metadata only)
DELETE/api/keys/:idRevoke an API key

Phone Numbers

MethodPathDescription
POST/api/routesRegister a phone number (max 20)
GET/api/routesList registered phone numbers
PUT/api/routes/primarySet primary phone number
DELETE/api/routes/:phoneRelease a phone number

Billing

MethodPathDescription
GET/api/billing/usageCurrent message count, limit, and plan
POST/api/billing/subscribeCreate Stripe checkout session
POST/api/billing/upgradeChange plan (prorated)
GET/api/billing/portalStripe billing portal URL

Health

MethodPathDescription
GET/Service info + WebSocket URL
GET/healthHealth check with git commit
GET/healthzHealth check with connection count

Plans & Limits

PlanPriceMessages/moTrial
Base$52507 days
Growth$152,0007 days
Plus$256,0007 days
Pro$5015,0007 days

Message limits are hard caps — sends fail with "Monthly message limit reached" when the limit is hit. Limits reset on each billing cycle.

Each account supports up to 20 concurrent WebSocket connections and 20 registered phone numbers. For higher-scale deployments, contact us.

Errors

WebSocket errors arrive as:

{
  "type": "error",
  "code": "unknown_type",
  "message": "Unknown message type: invalid"
}
CodeMeaning
unknown_typeInvalid message type sent
invalid_syncMissing or invalid "since" field in sync request
sync_errorDatabase error during message replay

send.result errors include the reason in the error field:

{
  "type": "send.result",
  "id": "msg-1",
  "ok": false,
  "error": "Monthly message limit reached"
}

Troubleshooting

"Server appears to be down"

The relay server is a WebSocket server. If you hit https://claw-messenger.onrender.com with a browser or curl, you'll get a JSON response confirming the server is up. The actual messaging endpoint is wss://claw-messenger.onrender.com/ws — it only accepts WebSocket connections.

Connection closes immediately

Messages not sending

Not receiving inbound messages

OpenClaw: "Not connected to claw-messenger"

Debugging message delivery

Diagnostic reports

The plugin (v0.1.7+) tracks connection events and errors automatically. Use the claw_messenger_diagnose tool to generate a report, or submit it to our server for analysis:

// OpenClaw: ask your agent to run the diagnose tool
// Or use the API directly:
POST /api/diagnostics
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "plugin_version": "0.1.7",
  "node_version": "v22.0.0",
  "errors": [{"ts": "...", "type": "error", "detail": "..."}],
  "connection_log": [{"ts": "...", "type": "connect"}, ...],
  "metadata": {"connected": true}
}

Reports are rate-limited to 1 per hour. The Claw Messenger team reviews reports and may reach out with fixes specific to your setup.