Pipes.bot

Media Handling

Receive, download, and send media files through WebSocket and the REST API.

Pipes.bot handles the full media lifecycle for WhatsApp messages. When someone sends a photo, voice note, or document to your pool number, Pipes.bot downloads it from WhatsApp, stores it temporarily, and includes a download URL in the WebSocket message.

Receiving media

When a media message arrives (image, audio, video, document, or sticker), the WebSocket event includes a media object:

{
  "type": "whatsapp_message",
  "data": {
    "type": "image",
    "body": "Check this out",
    "media": {
      "mediaId": "aBcDeFgHiJkLmNoPqRs1t",
      "downloadUrl": "/v1/media/download/aBcDeFgHiJkLmNoPqRs1t",
      "mimeType": "image/jpeg",
      "byteSize": 245120
    }
  }
}

Media fields

FieldTypeDescription
mediaIdstring?Obfuscated media ID for downloading
downloadUrlstring?Relative URL path: /v1/media/download/{mediaId}
mimeTypestringMIME type (e.g. image/jpeg, audio/ogg)
fileNamestring?Original file name, mainly present for documents
byteSizenumberFile size in bytes
unavailableboolean?true if the media could not be downloaded from WhatsApp

Unavailable media

If Pipes.bot cannot download the file from WhatsApp (network error, expired CDN link), the message is still delivered with unavailable: true. The mediaId and downloadUrl fields will be absent.

{
  "media": {
    "mimeType": "image/jpeg",
    "byteSize": 0,
    "unavailable": true
  }
}

Always check for this flag before attempting a download.

Downloading media

Use the media ID from the WebSocket message to download the file:

GET /v1/media/download/{mediaId}
Authorization: Bearer YOUR_API_KEY

The endpoint validates your API key and tenant ownership, then redirects (302) to a short-lived presigned URL (5-minute expiry).

Node.js example

async function downloadMedia(mediaId: string, apiKey: string): Promise<Buffer> {
  const response = await fetch(`https://api.pipes.bot/v1/media/download/${mediaId}`, {
    headers: { Authorization: `Bearer ${apiKey}` },
    redirect: "follow",
  });

  if (!response.ok) {
    throw new Error(`Download failed: ${response.status}`);
  }

  return Buffer.from(await response.arrayBuffer());
}

Python example

import requests

def download_media(media_id: str, api_key: str) -> bytes:
    response = requests.get(
        f"https://api.pipes.bot/v1/media/download/{media_id}",
        headers={"Authorization": f"Bearer {api_key}"},
        allow_redirects=True,
    )
    response.raise_for_status()
    return response.content

Media expiry

Media is retained for 48 hours from receipt. After that, the file is automatically deleted. Download and store files in your own storage if you need them longer.

Expired media returns 410 Gone:

{ "error": "Media expired", "code": "MEDIA_EXPIRED" }

Download errors

StatusCodeDescription
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENMedia belongs to a different tenant
404NOT_FOUNDMedia ID does not exist
410MEDIA_EXPIREDMedia has expired (48-hour TTL)

Sending media

Send media messages through the REST API. The media field auto-detects the input mode.

POST /v1/messages/send
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Using a media ID

Reference a previously received or uploaded file by its 21-character media ID:

{
  "conversationId": "conv_xyz789",
  "media": "aBcDeFgHiJkLmNoPqRs1t",
  "caption": "Here's that photo"
}

Using a URL

Provide an HTTPS URL and Pipes.bot fetches the file. Private/internal addresses are blocked.

{
  "conversationId": "conv_xyz789",
  "media": "https://example.com/files/report.pdf",
  "caption": "Monthly report"
}

Using base64

Send file data inline. The mimeType field is required.

{
  "conversationId": "conv_xyz789",
  "media": "iVBORw0KGgoAAAANSUhEUg...",
  "mimeType": "image/png",
  "fileName": "screenshot.png",
  "caption": "See attached"
}

Request fields

FieldTypeDescription
conversationIdstringRequired. Target conversation ID
textstring?Text content. Used as caption for media if caption is absent
mediastring?Media ID, HTTPS URL, or base64 data. Either text or media must be provided
mimeTypestring?MIME type. Required for base64 mode
fileNamestring?File name. Optional, used for base64 mode
captionstring?Media caption (max 1024 chars). Takes precedence over text

Response

{
  "success": true,
  "messageId": "wamid.abc123...",
  "conversationId": "conv_xyz789"
}

Send errors

StatusCodeDescription
400INVALID_REQUESTMissing required fields or invalid input
400INVALID_MEDIAUnsupported MIME type or file exceeds size limit
400INVALID_URLURL blocked by SSRF protection
400FETCH_FAILEDCould not fetch media from URL
403FORBIDDENConversation or media belongs to another tenant
404NOT_FOUNDConversation or media ID not found
410MEDIA_EXPIREDReferenced media has expired
500META_UPLOAD_FAILEDFailed to upload to WhatsApp
500SEND_FAILEDWhatsApp rejected the message

Uploading media

Upload media independently before sending. Useful for reusing the same file across multiple messages.

POST /v1/media/upload
Authorization: Bearer YOUR_API_KEY

Multipart file upload

curl -X POST https://api.pipes.bot/v1/media/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg"

JSON with URL

curl -X POST https://api.pipes.bot/v1/media/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/files/photo.jpg" }'

JSON with base64

curl -X POST https://api.pipes.bot/v1/media/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": "iVBORw0KGgo...", "mimeType": "image/png", "fileName": "chart.png" }'

Response

{ "mediaId": "aBcDeFgHiJkLmNoPqRs1t" }

Uploaded media expires after 48 hours.

Supported media types

WhatsApp enforces format and size limits per media type:

TypeMax sizeAccepted formats
Image5 MBimage/jpeg, image/png
Audio16 MBaudio/aac, audio/mp4, audio/mpeg, audio/amr, audio/ogg
Video16 MBvideo/mp4, video/3gpp
Sticker500 KBimage/webp
Document100 MBAny MIME type

Files that don't match a specific type are sent as documents.

Complete example

A WebSocket handler that receives an image, processes it, and sends it back:

ws.on("message", async (raw) => {
  const event = JSON.parse(raw.toString());
  if (event.type !== "whatsapp_message") return;

  const msg = event.data;

  if (msg.type === "image" && msg.media && !msg.media.unavailable) {
    // Download the original image
    const imageBuffer = await downloadMedia(msg.media.mediaId, API_KEY);

    // Process the image (your logic)
    const processed = await addWatermark(imageBuffer);

    // Send it back via REST API
    await fetch("https://api.pipes.bot/v1/messages/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        conversationId: msg.conversationId,
        media: processed.toString("base64"),
        mimeType: "image/jpeg",
        caption: "Here's your watermarked image",
      }),
    });
  }
});

On this page