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 pool numbers or conversations for granular control.
How it works
- Connect to the WebSocket endpoint with your API key (and optionally a
poolNumberIdand/orconversationId) - 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",
"authType": "user"
}Pool-number-scoped connection (filtered by pool number)
Open a WebSocket connection to receive only messages from a specific pool number:
wss://api.pipes.bot/ws?apiKey=YOUR_API_KEY&poolNumberId=POOL_IDWelcome message:
{
"type": "connected",
"userId": "user_abc123",
"connectionId": "550e8400-e29b-41d4-a716-446655440000",
"scope": "poolNumber",
"authType": "user",
"poolNumberId": "pool_123"
}Conversation-scoped connection (filtered by conversation)
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",
"authType": "user",
"conversationId": "conv_xyz789"
}App-scoped connection (BYON ak_ key)
Authenticate with an App's ak_ key to receive messages for all the App's BYON numbers:
wss://api.pipes.bot/ws?apiKey=ak_your_app_keyWelcome message:
{
"type": "connected",
"userId": "user_abc123",
"connectionId": "550e8400-e29b-41d4-a716-446655440000",
"scope": "app",
"authType": "app",
"appId": "app_abc123"
}Authentication
Two key types can open a connection:
- User keys (
pk_+ 32 chars): the first 11 characters are a lookup prefix; the full key is verified against a bcrypt hash. Connections getscope: "user"(orpoolNumber/conversationwhen filtered) andauthType: "user". - App keys (
ak_, BYON): open an app-scoped connection. These getscope: "app"andauthType: "app", are tracked perappId, receive BYON messages for that App, and drain app-scoped offline queues on connect (see Offline Queuing).
Invalid or missing keys receive an HTTP 401 Unauthorized before the WebSocket upgrade completes.
Invalid poolNumberId values (not found, not owned, or not activated) receive an HTTP 403 Forbidden before the upgrade completes.
Invalid conversationId values (not found or not owned) receive an HTTP 403 Forbidden before the 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
Pool-number-scoped connections (with poolNumberId parameter):
- Receive only messages from the specified pool number
- No queue drain on connect (to avoid partial queue complexity)
- Can only reply to conversations belonging to that pool number
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)
Combined scoping (with both conversationId and poolNumberId):
- You can pass both
conversationIdandpoolNumberIdtogether. The server validates that the conversation belongs to the specified pool number - Receive only messages matching that specific conversation on that pool number
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
- Pool-number-scoped connections receive it only if poolNumberId matches
- Conversation-scoped connections receive it only if conversationId matches
- Use cases: dashboard with per-conversation chat panels, per-number bots, 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;
}
});Pool-number-scoped connection (filtered by pool number)
import WebSocket from "ws";
const API_KEY = "pk_your_api_key_here";
const POOL_NUMBER_ID = "pool_123";
const ws = new WebSocket(`wss://api.pipes.bot/ws?apiKey=${API_KEY}&poolNumberId=${POOL_NUMBER_ID}`);
ws.on("message", (data) => {
const event = JSON.parse(data.toString());
switch (event.type) {
case "connected":
console.log("Connected, scope:", event.scope); // "poolNumber"
console.log("Scoped to pool number:", event.poolNumberId); // "pool_123"
break;
case "whatsapp_message":
// Only messages for pool_123 arrive here
console.log("Message from", event.data.fromNumber);
console.log("Conversation:", event.data.conversationId);
console.log("Text:", event.data.text);
// Reply (must be a conversation belonging to pool_123)
ws.send(JSON.stringify({
type: "whatsapp_reply",
data: {
conversationId: event.data.conversationId,
poolNumberId: event.data.poolNumberId,
toNumber: event.data.fromNumber,
text: "Reply from this pool number's bot",
},
}));
break;
}
});Conversation-scoped connection (filtered by conversation)
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.