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 HTTP or HTTPS URL and Pipes.bot fetches the file for you (HTTPS strongly recommended). 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 (max 4096 chars, non-empty when present). Used as caption for media messages if caption is absent |
media | string? | Media ID, HTTP(S) 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.
BYON numbers are exempt. The 24-hour window check is skipped entirely for conversations on BYON-type pool numbers — you can send to them at any time.
Conversations with no inbound yet. If a conversation has never received an inbound message, the send is rejected with 403 SERVICE_WINDOW_EXPIRED and the message "No inbound messages received in this conversation".
Rate limits
Rate limiting applies only to the message-send endpoints (POST /v1/messages/send, plus POST /v1/messages/app/send and POST /v1/messages/passthrough). Each is limited to 60 requests per minute per API key. The media upload/download endpoints are not rate-limited.
Rate-limit headers are returned only on the send endpoints:
| 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. The limiter is in-memory per server instance (it resets on restart and is not shared across instances).
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 |
400 | UPLOAD_FAILED | Media upload failed while processing the request |
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 expired (48-hour TTL) or the stored file is missing from storage |
429 | RATE_LIMITED | Too many requests, retry after reset |
500 | META_UPLOAD_FAILED | Failed to upload media to WhatsApp |
500 | UPLOAD_FAILED | Media record lookup failed after upload |
500 | SEND_FAILED | WhatsApp rejected the message |
UPLOAD_FAILEDis intentionally reused across two paths: it returns400when media processing fails during the request, and500when the media record can't be found after a successful upload. Branch on the HTTP status, not the code alone, to tell them apart.
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",
}),
});
}
});