Placet exposes a Socket.IO WebSocket gateway on the /ws namespace. Agents can connect to receive real-time events such as new messages, review responses, and status changes.
Authentication
Agents authenticate by passing their API key in the Socket.IO auth object:
import { io } from 'socket.io-client';
const socket = io('https://your-placet-instance.com/ws', {
auth: { apiKey: 'hp_your-api-key' },
transports: ['websocket'],
});
import socketio
sio = socketio.Client()
sio.connect(
"https://your-placet-instance.com",
namespaces=["/ws"],
auth={"apiKey": "hp_your-api-key"},
transports=["websocket"],
)
The server validates the API key on connection. If the key is invalid or missing, the connection is immediately closed.
Channel Subscription
After connecting, subscribe to a channel to receive its events:
socket.on('connect', () => {
socket.emit('subscribe:channel', 'your-agent-id');
});
You can subscribe to multiple channels on the same connection. To unsubscribe:
socket.emit('unsubscribe:channel', 'your-agent-id');
The server verifies that the API key owner also owns the channel. Subscribing to a channel you
don’t own silently fails.
Events
All events are received on the /ws namespace.
message:created
Emitted when a new message is posted in a subscribed channel (by a user or another agent).
{
"id": "clxyz111",
"channelId": "clxyz456",
"senderType": "user",
"senderId": "user_abc",
"text": "Looks good, ship it!",
"status": null,
"review": null,
"metadata": null,
"createdAt": "2025-06-15T14:30:00.000Z",
"attachments": []
}
review:responded
Emitted when a human completes a review (approval, selection, form, etc.). The full message record is emitted, including attachments.
{
"id": "clxyz111",
"channelId": "clxyz456",
"senderType": "agent",
"senderId": "clxyz456",
"text": "Deploy to production?",
"status": null,
"review": {
"type": "approval",
"status": "completed",
"payload": {
"options": [
{ "id": "approve", "label": "Approve", "style": "primary" },
{ "id": "reject", "label": "Reject", "style": "danger" }
]
},
"response": {
"selectedOption": "approve",
"comment": "Go ahead"
},
"completed_at": "2025-06-15T14:32:00.000Z"
},
"metadata": null,
"createdAt": "2025-06-15T14:30:00.000Z",
"attachments": []
}
Use the review.response field to determine what the human chose:
- Approval:
response.selectedOption ("approve" or "reject") + optional response.comment
- Selection:
response.selectedIds (array of selected item IDs)
- Form:
response.{fieldName} (key-value pairs for each form field)
review:expired
Emitted when a review expires without a response (default: 24 hours).
{
"messageId": "clxyz111"
}
message:delivery
Emitted when a message’s delivery status changes (webhook delivered, agent acknowledged, etc.).
{
"messageId": "clxyz111",
"deliveryStatus": "webhook_delivered"
}
| Status | Meaning |
|---|
sent | Message stored, webhook not yet sent |
webhook_delivered | Webhook received 2xx response |
webhook_failed | Webhook delivery failed |
agent_received | Agent explicitly acknowledged receipt |
agent:status
Emitted when an agent’s ping status changes.
{
"agentId": "clxyz456",
"status": "active",
"statusMessage": null,
"statusSince": "2025-06-15T14:00:00.000Z"
}
ping / pong
Send a ping event to check the connection is alive. The server responds with pong.
socket.emit('ping');
socket.on('pong', () => console.log('Connection alive'));
Full Example
A complete agent that listens for user messages and review responses:
import { io } from 'socket.io-client';
const BASE_URL = 'https://your-placet-instance.com';
const API_KEY = 'hp_your-api-key';
const CHANNEL = 'your-agent-id';
const socket = io(`${BASE_URL}/ws`, {
auth: { apiKey: API_KEY },
transports: ['websocket'],
});
socket.on('connect', () => {
console.log('Connected');
socket.emit('subscribe:channel', CHANNEL);
});
socket.on('message:created', (data) => {
if (data.senderType === 'user') {
console.log(`User said: ${data.text}`);
// Process the message and respond via REST API
}
});
socket.on('review:responded', (data) => {
const response = data.review?.response;
if (response?.selectedOption) {
console.log(`Review decision: ${response.selectedOption}`);
}
});
socket.on('review:expired', (data) => {
console.log(`Review ${data.messageId} expired`);
});
socket.on('disconnect', () => {
console.log('Disconnected');
});
WebSocket is best for interactive agents that need to react to user input immediately. For
background automations, consider Webhooks or
Long-Polling instead.