WebSocket
Receive WhatsApp messages in real time over a persistent WebSocket connection.
Pipes.bot streams incoming WhatsApp messages to your application over WebSocket connections. This is the default delivery method — no public URL required.
You can open one or more connections per account, optionally scoped to specific conversations for granular control.
How it works
- Connect to the WebSocket endpoint with your API key (and optionally a conversationId)
- Receive a
connectedconfirmation - Incoming WhatsApp messages arrive as
whatsapp_messageevents - Send text replies back through the same connection
- If you disconnect, messages are queued and delivered when you reconnect (user-scoped connections only)
Connecting
User-scoped connection (all messages)
Open a WebSocket connection to receive all messages from all your activated pool numbers:
wss://api.pipes.bot/ws?apiKey=YOUR_API_KEYWelcome message:
{
"type": "connected",
"userId": "user_abc123",
"connectionId": "550e8400-e29b-41d4-a716-446655440000",
"scope": "user"
}Conversation-scoped connection (filtered messages)
Open a WebSocket connection to receive only messages for a specific conversation:
wss://api.pipes.bot/ws?apiKey=YOUR_API_KEY&conversationId=CONV_IDWelcome message:
{
"type": "connected",
"userId": "user_abc123",
"connectionId": "550e8400-e29b-41d4-a716-446655440000",
"scope": "conversation",
"conversationId": "conv_xyz789"
}Authentication
API keys follow the format pk_ followed by 32 random characters. The first 11 characters serve as a lookup prefix; the full key is verified against a bcrypt hash.
Invalid or missing keys receive an HTTP 401 Unauthorized before the WebSocket upgrade completes.
Invalid conversationIds (not found or not owned by the authenticated user) receive an HTTP 403 Forbidden before the WebSocket upgrade completes.
Connection scoping
User-scoped connections (no conversationId parameter):
- Receive all messages from all activated pool numbers
- Queue drain happens on connect: all queued messages are delivered
- Can reply to any owned conversation
Conversation-scoped connections (with conversationId parameter):
- Receive only messages matching the specified conversationId
- No queue drain on connect (to avoid partial queue complexity)
- Can only reply to the scoped conversationId (replies to other conversations are rejected)
Multiple connections:
- You can open multiple connections simultaneously for the same account
- Messages are broadcast to ALL matching connections:
- User-scoped connections always receive the message
- Conversation-scoped connections receive it only if conversationId matches
- Use cases: dashboard with per-conversation chat panels, multiple bots on different conversations, one firehose + specific monitors
Keepalive
Send WebSocket ping frames at regular intervals (25 seconds recommended) to keep the connection alive through proxies and load balancers. The server responds with pong automatically.
Reconnection
When the connection drops, reconnect with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 4 | 8 seconds |
| ... | ... |
| Max | 30 seconds |
Reset the backoff counter after a successful connection.
Quick example
User-scoped connection (all messages)
import WebSocket from "ws";
const API_KEY = "pk_your_api_key_here";
const ws = new WebSocket(`wss://api.pipes.bot/ws?apiKey=${API_KEY}`);
ws.on("open", () => {
console.log("Connected to Pipes.bot");
});
ws.on("message", (data) => {
const event = JSON.parse(data.toString());
switch (event.type) {
case "connected":
console.log("Authenticated as", event.userId);
console.log("Scope:", event.scope); // "user"
break;
case "whatsapp_message":
console.log("Message from", event.data.fromNumber);
console.log("Conversation:", event.data.conversationId);
console.log("Type:", event.data.type);
console.log("Text:", event.data.text);
// Send a reply
ws.send(JSON.stringify({
type: "whatsapp_reply",
data: {
conversationId: event.data.conversationId,
poolNumberId: event.data.poolNumberId,
toNumber: event.data.fromNumber,
text: "Thanks for your message!",
},
}));
break;
case "reply_status":
if (event.data.success) {
console.log("Reply sent:", event.data.messageId);
} else {
console.error("Reply failed:", event.data.error);
}
break;
}
});Conversation-scoped connection (filtered)
import WebSocket from "ws";
const API_KEY = "pk_your_api_key_here";
const CONVERSATION_ID = "conv_xyz789";
const ws = new WebSocket(`wss://api.pipes.bot/ws?apiKey=${API_KEY}&conversationId=${CONVERSATION_ID}`);
ws.on("message", (data) => {
const event = JSON.parse(data.toString());
switch (event.type) {
case "connected":
console.log("Connected, scope:", event.scope); // "conversation"
console.log("Scoped to:", event.conversationId); // "conv_xyz789"
break;
case "whatsapp_message":
// Only messages for conv_xyz789 arrive here
console.log("Message from", event.data.fromNumber);
console.log("Text:", event.data.text);
// Reply (must match the connection's conversationId)
ws.send(JSON.stringify({
type: "whatsapp_reply",
data: {
conversationId: event.data.conversationId, // Must be conv_xyz789
poolNumberId: event.data.poolNumberId,
toNumber: event.data.fromNumber,
text: "Reply to this specific conversation",
},
}));
break;
}
});Choosing between WebSocket and Webhook
| WebSocket | Webhook | |
|---|---|---|
| Connection | Persistent, bidirectional | Stateless HTTP POST |
| Best for | Real-time apps, bots with instant replies | Serverless functions, background processing |
| Requires | Long-lived process | Publicly accessible URL |
| Offline handling | Messages queued until reconnect | Retried with backoff, then queued |
You can switch delivery method at any time from the pool numbers dashboard without losing messages.