Pipes.bot

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

  1. Connect to the WebSocket endpoint with your API key (and optionally a conversationId)
  2. Receive a connected confirmation
  3. Incoming WhatsApp messages arrive as whatsapp_message events
  4. Send text replies back through the same connection
  5. 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_KEY

Welcome 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_ID

Welcome 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:

AttemptDelay
11 second
22 seconds
34 seconds
48 seconds
......
Max30 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

WebSocketWebhook
ConnectionPersistent, bidirectionalStateless HTTP POST
Best forReal-time apps, bots with instant repliesServerless functions, background processing
RequiresLong-lived processPublicly accessible URL
Offline handlingMessages queued until reconnectRetried with backoff, then queued

You can switch delivery method at any time from the pool numbers dashboard without losing messages.

On this page