Skip to main content

Messages API

The Messages API allows you to send messages via configured messaging channels (Signal, Telegram, Discord, etc.).

Send Message

Send a message to a recipient via a messaging channel.

Method: send

Request:
{
  "jsonrpc": "2.0",
  "id": 20,
  "method": "send",
  "params": {
    "to": "+1234567890",
    "message": "Hello from OpenClaw!",
    "channel": "signal",
    "idempotencyKey": "unique-key-123"
  }
}
to
string
required
Recipient identifier (phone number, user ID, username, etc.)
message
string
Text message content (at least one of message, mediaUrl, or mediaUrls required)
channel
string
Channel ID (e.g., "signal", "telegram", "discord", "slack")
idempotencyKey
string
required
Unique key to prevent duplicate sends
mediaUrl
string
Single media attachment URL
mediaUrls
array
Array of media attachment URLs
gifPlayback
boolean
default:false
Enable GIF playback (for animated GIFs)
accountId
string
Account ID for multi-account channels
threadId
string
Thread ID for threaded messages
sessionKey
string
Session key for tracking conversation context
Response:
{
  "id": 20,
  "ok": true,
  "payload": {
    "sent": true,
    "messageId": "msg-123"
  }
}
sent
boolean
Whether the message was sent successfully
messageId
string
Platform-specific message identifier
cached
boolean
Whether the response was cached (idempotency)

Send Message with Media

Send messages with image, video, or document attachments.

Single Media Attachment

{
  "jsonrpc": "2.0",
  "id": 21,
  "method": "send",
  "params": {
    "to": "+1234567890",
    "message": "Check out this image!",
    "mediaUrl": "https://example.com/image.jpg",
    "channel": "signal",
    "idempotencyKey": "unique-key-124"
  }
}

Multiple Media Attachments

{
  "jsonrpc": "2.0",
  "id": 22,
  "method": "send",
  "params": {
    "to": "+1234567890",
    "message": "Photo album:",
    "mediaUrls": [
      "https://example.com/photo1.jpg",
      "https://example.com/photo2.jpg",
      "https://example.com/photo3.jpg"
    ],
    "channel": "signal",
    "idempotencyKey": "unique-key-125"
  }
}

Media-Only Message

{
  "jsonrpc": "2.0",
  "id": 23,
  "method": "send",
  "params": {
    "to": "+1234567890",
    "mediaUrl": "https://example.com/document.pdf",
    "channel": "signal",
    "idempotencyKey": "unique-key-126"
  }
}

Idempotency

The idempotencyKey parameter ensures duplicate messages are not sent:
  • Same idempotencyKey within a short time window returns cached result
  • Prevents accidental duplicate sends
  • Returns cached: true in response metadata
Example cached response:
{
  "id": 20,
  "ok": true,
  "payload": {
    "sent": true,
    "messageId": "msg-123"
  },
  "meta": {
    "cached": true
  }
}

Channels

Available channels depend on your configuration:
ChannelIDRecipient Format
SignalsignalPhone number (E.164): "+1234567890"
TelegramtelegramUser ID or username: "@username"
DiscorddiscordUser ID or channel ID
SlackslackUser ID or channel ID: "#channel"
WhatsAppwhatsappPhone number (E.164)
iMessageimessagePhone number or email
See Channels for configuration details.

Message Routing

OpenClaw automatically routes messages based on:
  1. Explicit channel - Use channel parameter
  2. Session key prefix - Extract channel from session key (e.g., "signal:+1234567890")
  3. Default channel - Use configured default channel
Example with session key routing:
{
  "method": "send",
  "params": {
    "to": "+1234567890",
    "message": "Hello!",
    "sessionKey": "signal:+1234567890",
    "idempotencyKey": "key-127"
  }
}
Channel is inferred from signal: prefix.

Polls

Send interactive polls via supported channels.

Method: poll

{
  "jsonrpc": "2.0",
  "id": 24,
  "method": "poll",
  "params": {
    "to": "+1234567890",
    "question": "What's your favorite color?",
    "options": ["Red", "Blue", "Green"],
    "channel": "signal",
    "idempotencyKey": "poll-123"
  }
}
question
string
required
Poll question
options
array
required
Array of poll options (strings)
to
string
required
Recipient identifier
channel
string
Channel ID
idempotencyKey
string
required
Unique key for idempotency
allowMultipleAnswers
boolean
default:false
Allow multiple selections
anonymous
boolean
default:false
Hide voter identities

Error Handling

Missing recipient:
{
  "ok": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Missing required parameter: to"
  }
}
Channel not configured:
{
  "ok": false,
  "error": {
    "code": "CHANNEL_NOT_FOUND",
    "message": "Channel 'signal' is not configured"
  }
}
Delivery failed:
{
  "ok": false,
  "error": {
    "code": "DELIVERY_FAILED",
    "message": "Failed to send message: recipient not found"
  }
}

Example: Send with Media

curl -X POST http://localhost:18789/api/send \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "send",
    "params": {
      "to": "+1234567890",
      "message": "Check this out!",
      "mediaUrl": "https://example.com/photo.jpg",
      "channel": "signal",
      "idempotencyKey": "msg-'$(date +%s)'"
    }
  }'

Example: JavaScript Client

const ws = new WebSocket('ws://localhost:18789');
let requestId = 0;

function sendMessage(to, message, channel = 'signal') {
  const id = ++requestId;
  const idempotencyKey = `msg-${Date.now()}-${id}`;
  
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id,
    method: "send",
    params: { to, message, channel, idempotencyKey }
  }));
  
  return id;
}

ws.on('open', () => {
  // Authenticate first
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 0,
    method: "connect",
    params: {
      role: 'control',
      auth: { token: 'your-token' },
      client: { name: 'Sender', version: '1.0.0' }
    }
  }));
});

ws.on('message', (data) => {
  const frame = JSON.parse(data);
  
  if (frame.id === 0 && frame.ok) {
    // Connected - send message
    sendMessage('+1234567890', 'Hello from WebSocket!');
  } else if (frame.id > 0) {
    console.log('Message sent:', frame.payload);
  }
});

Message Format

Messages support basic formatting depending on the channel: Markdown (Signal, Telegram, Discord, Slack):
**bold** _italic_ `code` [link](https://example.com)
Mentions:
@username mentioned you!
Emojis:
Hello! 👋
Formatting support varies by channel. See individual channel documentation.

Best Practices

Generate unique keys to prevent duplicate sends:
const idempotencyKey = `msg-${Date.now()}-${Math.random()}`;
Ensure phone numbers are in E.164 format:
+1234567890  ✓
1234567890   ✗
(123)456-7890 ✗
  • Use publicly accessible URLs
  • Ensure HTTPS for secure content
  • Check file size limits per channel
Verify channel is logged in before sending:
{"method": "channels.status", "params": {}}

Next Steps

Channels

Configure messaging channels

Sessions

Manage conversation sessions

Agent Protocol

Invoke agents to generate responses