Pipes.bot

Webhook Payload

Structure of the webhook payload delivered to your endpoint.

Pipes.bot delivers a Meta-compatible webhook payload extended with a pipes object for Pipes.bot-specific metadata including media download information.

Payload structure

Every webhook delivery follows the same envelope structure. The messages[].type field determines the shape of the type-specific payload, and the pipes object carries media metadata when applicable.

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "pool_number_id",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "+15551234567",
              "phone_number_id": "pool_number_id"
            },
            "messages": [
              {
                "id": "msg_abc123",
                "from": "15559876543",
                "timestamp": "2025-01-15T10:30:00.000Z",
                "type": "text",
                "text": {
                  "body": "Hello from WhatsApp!"
                }
              }
            ],
            "contacts": [
              {
                "profile": {
                  "name": "Jane Doe"
                },
                "wa_id": "15559876543"
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ],
  "pipes": {
    "conversationId": "conv_xyz789",
    "poolNumberId": "pool_number_id",
    "label": "support"
  }
}

Field reference

Root fields

FieldTypeDescription
objectstringAlways "whatsapp_business_account"
entryarrayArray containing one entry object
pipesobjectPipes.bot extension fields (see below)

entry[].changes[].value

FieldTypeDescription
messaging_productstringAlways "whatsapp"
metadata.display_phone_numberstringYour pool number's phone number
metadata.phone_number_idstringYour pool number ID
messagesarrayArray containing one message object
contactsarrayArray containing one contact object

messages[]

FieldTypeDescription
idstringUnique message identifier
fromstringSender's WhatsApp number
timestampstringISO 8601 timestamp
typestringMessage type: text, image, audio, document, video, sticker, location, contacts, reaction

The remaining fields depend on the message type. See Message types below.

contacts[]

FieldTypeDescription
profile.namestringSender's WhatsApp profile name
wa_idstringSender's WhatsApp ID

pipes

FieldTypeDescription
conversationIdstringPipes.bot conversation ID
poolNumberIdstringPool number this message was received on
labelstring?Activation label, if one was set
testboolean?true only on test webhook deliveries
mediaobject?Media metadata, present for media message types

pipes.media

Present when the message type is image, audio, document, video, or sticker.

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

When unavailable is true, mediaId and downloadUrl will be absent. The message is still delivered so you can notify the user, but the file cannot be retrieved.

Message types

Text

{
  "type": "text",
  "text": {
    "body": "Hello from WhatsApp!"
  }
}

Image

Images include an optional caption. The file can be downloaded via pipes.media.

{
  "type": "image",
  "image": {
    "id": "media_id",
    "mime_type": "image/jpeg",
    "caption": "Check this out"
  }
}
pipes object
{
  "pipes": {
    "conversationId": "conv_xyz789",
    "poolNumberId": "pool_number_id",
    "media": {
      "mediaId": "aBcDeFgHiJkLmNoPqRs1t",
      "downloadUrl": "/v1/media/download/aBcDeFgHiJkLmNoPqRs1t",
      "mimeType": "image/jpeg",
      "byteSize": 245120
    }
  }
}

Audio

Audio messages include voice notes and audio file attachments.

{
  "type": "audio",
  "audio": {
    "id": "media_id",
    "mime_type": "audio/ogg"
  }
}

Video

Videos include an optional caption.

{
  "type": "video",
  "video": {
    "id": "media_id",
    "mime_type": "video/mp4",
    "caption": "Watch this"
  }
}

Document

Documents include the original file name and an optional caption.

{
  "type": "document",
  "document": {
    "id": "media_id",
    "mime_type": "application/pdf",
    "filename": "invoice.pdf",
    "caption": "Here's the invoice"
  }
}

Sticker

{
  "type": "sticker",
  "sticker": {
    "id": "media_id",
    "mime_type": "image/webp"
  }
}

Location

{
  "type": "location",
  "location": {
    "latitude": 37.7749,
    "longitude": -122.4194,
    "name": "San Francisco",
    "address": "San Francisco, CA, USA"
  }
}

Location messages do not include pipes.media.

Contacts

{
  "type": "contacts",
  "contacts": [
    {
      "name": { "formatted_name": "Jane Doe" },
      "phones": [{ "phone": "+15559876543", "type": "CELL" }]
    }
  ]
}

Reaction

Reactions reference the original message ID. An absent emoji means the reaction was removed.

{
  "type": "reaction",
  "reaction": {
    "message_id": "msg_original123",
    "emoji": "👍"
  }
}

Test payloads

Test payloads sent via the Test Connection button include pipes.test: true. Use this flag to filter out test deliveries:

app.post("/webhook", (req, res) => {
  const payload = req.body;

  if (payload.pipes?.test) {
    // Acknowledge test delivery without processing
    return res.sendStatus(200);
  }

  // Process real message...
});

Handling all message types

Here's a complete example that routes by message type:

app.post("/webhook", (req, res) => {
  const payload = req.body;

  if (payload.pipes?.test) {
    return res.sendStatus(200);
  }

  const message = payload.entry[0].changes[0].value.messages[0];
  const pipes = payload.pipes;

  switch (message.type) {
    case "text":
      console.log("Text:", message.text.body);
      break;

    case "image":
    case "audio":
    case "video":
    case "document":
    case "sticker":
      if (pipes.media?.unavailable) {
        console.log("Media unavailable for this message");
      } else {
        console.log("Download from:", pipes.media.downloadUrl);
        console.log("MIME type:", pipes.media.mimeType);
        console.log("Size:", pipes.media.byteSize, "bytes");
      }
      break;

    case "location":
      console.log("Location:", message.location.latitude, message.location.longitude);
      break;

    case "contacts":
      console.log("Contacts:", message.contacts);
      break;

    case "reaction":
      console.log("Reaction:", message.reaction.emoji, "on", message.reaction.message_id);
      break;
  }

  res.sendStatus(200);
});

On this page