Sending Messages
Send text and media messages to WhatsApp conversations via the REST API.
Send outbound WhatsApp messages through the Pipes.bot REST API. You can send text, images, audio, video, documents, and stickers — either by referencing a media ID, providing a URL, or sending base64 data inline.
All send operations use the same endpoint:
POST /v1/messages/send
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonSending text
{
"conversationId": "conv_xyz789",
"text": "Thanks for reaching out! We'll get back to you shortly."
}Response
{
"success": true,
"messageId": "wamid.abc123...",
"conversationId": "conv_xyz789"
}Sending media
Include a media field to send media. Pipes.bot auto-detects the input mode based on the value.
Using a media ID
Reference a previously received or uploaded file by its 21-character media ID. The file keeps its 48-hour TTL and can be reused across multiple sends.
{
"conversationId": "conv_xyz789",
"media": "aBcDeFgHiJkLmNoPqRs1t",
"caption": "Here's that photo"
}Using a URL
Provide an HTTPS URL and Pipes.bot fetches the file for you. Private and internal addresses are blocked (SSRF protection).
{
"conversationId": "conv_xyz789",
"media": "https://example.com/files/report.pdf",
"caption": "Monthly report"
}Using base64
Send file data inline as a base64-encoded string. The mimeType field is required.
{
"conversationId": "conv_xyz789",
"media": "iVBORw0KGgoAAAANSUhEUg...",
"mimeType": "image/png",
"fileName": "screenshot.png",
"caption": "See attached"
}Request fields
| Field | Type | Description |
|---|---|---|
conversationId | string | Required. Target conversation ID |
text | string? | Text content. Used as caption for media messages if caption is absent |
media | string? | Media ID, HTTPS URL, or base64 data. Either text or media must be provided |
mimeType | string? | MIME type. Required for base64 mode |
fileName | string? | File name. Optional, used for base64 mode |
caption | string? | Media caption (max 1024 chars). Takes precedence over text |
Uploading media
Upload media independently before sending. This is useful when you want to reuse the same file across multiple messages or conversations.
POST /v1/media/upload
Authorization: Bearer YOUR_API_KEYMultipart 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" }'Upload response
{ "mediaId": "aBcDeFgHiJkLmNoPqRs1t" }Use the returned mediaId in the media field of the send endpoint. Uploaded media expires after 48 hours.
Supported media types
WhatsApp enforces format and size limits per media type:
| Type | Max size | Accepted formats |
|---|---|---|
| Image | 5 MB | image/jpeg, image/png |
| Audio | 16 MB | audio/aac, audio/mp4, audio/mpeg, audio/amr, audio/ogg |
| Video | 16 MB | video/mp4, video/3gpp |
| Sticker | 500 KB | image/webp |
| Document | 100 MB | Any MIME type |
Files that don't match a specific media type (image, audio, video, sticker) are sent as documents.
24-hour service window
WhatsApp enforces a 24-hour messaging window. You can only send outbound messages within 24 hours of the customer's last inbound message. After the window expires, the API returns:
{ "error": "Service window expired...", "code": "SERVICE_WINDOW_EXPIRED" }The window resets each time the customer sends a new message.
Rate limits
The API enforces 60 requests per minute per API key across all endpoints. Rate limit headers are included in every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window (60) |
X-RateLimit-Remaining | Requests remaining in this window |
X-RateLimit-Reset | Unix timestamp when window resets |
Exceeding the limit returns 429 Too Many Requests.
Error reference
| Status | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | Missing required fields or invalid input |
400 | INVALID_MEDIA | Unsupported MIME type or file exceeds size limit |
400 | INVALID_URL | URL blocked by SSRF protection |
400 | FETCH_FAILED | Could not fetch media from URL |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | FORBIDDEN | Conversation or media belongs to another tenant |
403 | SERVICE_WINDOW_EXPIRED | Last inbound message is older than 24 hours |
404 | NOT_FOUND | Conversation or media ID not found |
410 | MEDIA_EXPIRED | Referenced media has expired (48-hour TTL) |
429 | RATE_LIMITED | Too many requests, retry after reset |
500 | META_UPLOAD_FAILED | Failed to upload media to WhatsApp |
500 | SEND_FAILED | WhatsApp rejected the message |
Complete examples
Send a text reply
const response = await fetch("https://api.pipes.bot/v1/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
conversationId: "conv_xyz789",
text: "Thanks for your message!",
}),
});
const result = await response.json();
console.log("Sent:", result.messageId);Forward a received image with a new caption
// msg.media.mediaId from a received whatsapp_message event
const response = await fetch("https://api.pipes.bot/v1/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
conversationId: "conv_xyz789",
media: msg.media.mediaId,
caption: "Forwarding this image",
}),
});Upload then send to multiple conversations
// Upload once
const uploadRes = await fetch("https://api.pipes.bot/v1/media/upload", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/promo-banner.jpg",
}),
});
const { mediaId } = await uploadRes.json();
// Send to multiple conversations
for (const convId of conversationIds) {
await fetch("https://api.pipes.bot/v1/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
conversationId: convId,
media: mediaId,
caption: "Check out our latest offer!",
}),
});
}Receive an image and send it back processed
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
const imageBuffer = await downloadMedia(msg.media.mediaId, API_KEY);
// Process (your logic)
const processed = await addWatermark(imageBuffer);
// Send back as base64
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",
}),
});
}
});