Pipes.bot
BYON

Passthrough Messages

Send template messages, interactive buttons, lists, CTA URL buttons, and other advanced WhatsApp message types via the passthrough endpoint.

The passthrough endpoint forwards your message payload directly to Meta's Cloud API, giving you access to every WhatsApp message type — templates, interactive buttons, list messages, locations, contacts, and reactions.

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

Both pool (pk_) and BYON (ak_) keys are accepted, but template messages require a BYON ak_ key.

Simple text messages

For sending plain text messages on BYON numbers, use the dedicated send endpoint:

POST /v1/messages/app/send
Authorization: Bearer ak_your_app_key
Content-Type: application/json
FieldTypeDescription
poolNumberIdstringRequired. Your BYON number ID
toNumberstringRequired on legacy. Recipient phone number, or a BSUID. On API v2 you can use to instead
tostring?API v2 only. Phone number or BSUID. Alias of toNumber; wins if both are sent
textstring?Message text. Either text or media must be provided
mediastring?Media ID, HTTP(S) URL, or base64 data (auto-detected)
mimeTypestring?MIME type. Required for base64 media
fileNamestring?Optional file name for media
captionstring?Media caption (max 1024 chars)
curl -X POST https://api.pipes.bot/v1/messages/app/send \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "poolNumberId": "pn_abc123",
    "toNumber": "5511999999999",
    "text": "Hello! Your order has been shipped."
  }'

On the legacy API the response is the flat Pipes.bot send shape:

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

On API v2 nothing is stored and the response is Meta's, trimmed to two fields:

{
  "messageId": "wamid.abc123...",
  "contacts": [{ "input": "US.13491208655302741918", "user_id": "US.13491208655302741918" }]
}

contacts is Meta's contacts array. When you message a BSUID, Meta answers with input and user_id and, in our tests, no wa_id: don't expect the send response to reveal a phone number. When you message a phone number, you get input and wa_id. If Meta rejects the message (for example with error 131062), Meta's error body is returned as-is with Meta's HTTP status. v2 adds these errors: 500 NO_CREDENTIALS (the number has no access token), 502 META_UNREACHABLE and 502 META_INVALID_RESPONSE.

For templates, interactive messages, location, contacts, and reactions, use the passthrough endpoint below.

Routing fields

The routing fields depend on your API key type and, for BYON, on your App's API version.

BYON (ak_ key), legacy:

FieldTypeDescription
messaging_productstringRequired. Always "whatsapp"
poolNumberIdstringRequired. Your BYON number ID
tostringRequired. Recipient phone number or BSUID
typestringRequired. Message type

poolNumberId and to are stripped, and Pipes.bot sets the recipient for Meta: to for a phone number, recipient for a BSUID.

BYON (ak_ key), API v2:

FieldTypeDescription
messaging_productstringRequired. Always "whatsapp"
poolNumberIdstringRequired. Your BYON number ID
to / recipientstringRequired. to for a phone number, recipient for a BSUID
typestringRequired. Message type

Only poolNumberId is stripped. The rest of the body is forwarded to Meta untouched.

Pool (pk_ key):

FieldTypeDescription
conversationIdstringRequired. Target conversation
typestringRequired. Message type

Sending template messages

Send a pre-approved template with dynamic parameters. Templates let you initiate conversations outside the 24-hour service window.

curl -X POST https://api.pipes.bot/v1/messages/passthrough \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "type": "template",
    "poolNumberId": "pn_abc123",
    "to": "5511999999999",
    "template": {
      "name": "order_confirmation",
      "language": { "code": "en_US" },
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "Maria" },
            { "type": "text", "text": "ORD-4521" },
            { "type": "text", "text": "January 20" }
          ]
        }
      ]
    }
  }'

Template with media header

{
  "messaging_product": "whatsapp",
  "type": "template",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "template": {
    "name": "shipping_update",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "image",
            "image": { "link": "https://example.com/tracking-map.jpg" }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "ORD-4521" },
          { "type": "text", "text": "Jan 20, 2025" }
        ]
      }
    ]
  }
}

Template with button parameters

For templates with dynamic URL buttons, pass the variable portion:

{
  "messaging_product": "whatsapp",
  "type": "template",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "template": {
    "name": "order_tracking",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "ORD-4521" }
        ]
      },
      {
        "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [
          { "type": "text", "text": "ORD-4521" }
        ]
      }
    ]
  }
}

Interactive reply buttons

Send a message with up to 3 reply buttons. When the user taps a button, you receive a webhook with the button ID.

curl -X POST https://api.pipes.bot/v1/messages/passthrough \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "type": "interactive",
    "poolNumberId": "pn_abc123",
    "to": "5511999999999",
    "interactive": {
      "type": "button",
      "header": {
        "type": "text",
        "text": "Confirm your appointment"
      },
      "body": {
        "text": "You have an appointment on Jan 20 at 3:00 PM. Would you like to confirm?"
      },
      "footer": {
        "text": "Reply within 24 hours"
      },
      "action": {
        "buttons": [
          {
            "type": "reply",
            "reply": { "id": "confirm", "title": "Confirm" }
          },
          {
            "type": "reply",
            "reply": { "id": "reschedule", "title": "Reschedule" }
          },
          {
            "type": "reply",
            "reply": { "id": "cancel", "title": "Cancel" }
          }
        ]
      }
    }
  }'

Button constraints

  • Max 3 buttons per message
  • Button title max 20 characters
  • Button id max 256 characters

Interactive list messages

Send a message with a selectable list of up to 10 items grouped into sections.

{
  "messaging_product": "whatsapp",
  "type": "interactive",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "interactive": {
    "type": "list",
    "header": {
      "type": "text",
      "text": "Our Menu"
    },
    "body": {
      "text": "Browse our menu and pick your items:"
    },
    "footer": {
      "text": "Prices may vary"
    },
    "action": {
      "button": "View Menu",
      "sections": [
        {
          "title": "Pizzas",
          "rows": [
            { "id": "margherita", "title": "Margherita", "description": "$12.00" },
            { "id": "pepperoni", "title": "Pepperoni", "description": "$14.00" }
          ]
        },
        {
          "title": "Drinks",
          "rows": [
            { "id": "cola", "title": "Cola", "description": "$3.00" },
            { "id": "water", "title": "Water", "description": "$2.00" }
          ]
        }
      ]
    }
  }
}

List constraints

  • Max 10 rows total across all sections
  • Max 10 sections
  • Row title max 24 characters
  • Row description max 72 characters
  • Action button text max 20 characters

Interactive CTA URL buttons

Send a message with a call-to-action button that opens a URL when tapped.

curl -X POST https://api.pipes.bot/v1/messages/passthrough \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "type": "interactive",
    "poolNumberId": "pn_abc123",
    "to": "5511999999999",
    "interactive": {
      "type": "cta_url",
      "header": {
        "type": "text",
        "text": "Visit our store"
      },
      "body": {
        "text": "Check out our latest products and promotions."
      },
      "footer": {
        "text": "Free shipping on orders over $50"
      },
      "action": {
        "name": "cta_url",
        "parameters": {
          "display_text": "Shop Now",
          "url": "https://example.com/store"
        }
      }
    }
  }'

CTA URL fields

FieldTypeDescription
interactive.typestringMust be "cta_url"
interactive.headerobjectOptional. Text header
interactive.bodyobjectRequired. Message body text
interactive.footerobjectOptional. Footer text
interactive.action.namestringRequired. Must be "cta_url"
action.parameters.display_textstringRequired. Button label (max 20 characters)
action.parameters.urlstringRequired. HTTPS URL to open

CTA URL constraints

  • display_text max 20 characters
  • url must use HTTPS

Sending location messages

{
  "messaging_product": "whatsapp",
  "type": "location",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "location": {
    "latitude": "-23.5505",
    "longitude": "-46.6333",
    "name": "Pipes.bot HQ",
    "address": "Av. Paulista, 1000 - São Paulo"
  }
}

Sending contact cards

{
  "messaging_product": "whatsapp",
  "type": "contacts",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "contacts": [
    {
      "name": { "formatted_name": "Support Team", "first_name": "Support" },
      "phones": [
        { "phone": "+15551234567", "type": "WORK" }
      ],
      "emails": [
        { "email": "support@example.com", "type": "WORK" }
      ]
    }
  ]
}

Sending reactions

React to a received message with an emoji:

{
  "messaging_product": "whatsapp",
  "type": "reaction",
  "poolNumberId": "pn_abc123",
  "to": "5511999999999",
  "reaction": {
    "message_id": "wamid.abc123...",
    "emoji": "👍"
  }
}

To remove a reaction, send an empty emoji string.

Response

All passthrough responses are returned directly from Meta:

{
  "messaging_product": "whatsapp",
  "contacts": [
    { "input": "5511999999999", "wa_id": "5511999999999" }
  ],
  "messages": [
    { "id": "wamid.abc123..." }
  ]
}

Error handling

Meta errors are forwarded as-is. Common errors:

Meta error codeMeaning
131047Re-engagement message outside window
131026Message undeliverable (user not on WA)
131053Media upload failed
132000Template parameter count mismatch
132015Template is paused
132012Template parameter format mismatch

Pipes.bot-level errors:

StatusCodeDescription
400INVALID_REQUESTMissing type, routing fields, or invalid JSON
401UNAUTHORIZEDMissing or invalid API key
403BYON_ONLYTemplate messages require a BYON ak_ key
403WINDOW_CLOSEDService window expired (pool users only)
404NOT_FOUNDConversation or BYON number not found
403FORBIDDENNumber not owned by your App, or conversation not yours
429RATE_LIMITED60 requests/minute exceeded
500NO_CREDENTIALSBYON number has no Meta access token configured
502META_UNREACHABLECould not reach Meta's API
502META_INVALID_RESPONSEMeta returned an unparseable/invalid response

BSUIDs and usernames

WhatsApp users can adopt a username and hide their phone number. When they message you, WhatsApp identifies them with a BSUID (business-scoped user ID) instead:

  • Format: CC.<id>, for example US.13491208655302741918. Parent BSUIDs (opt-in, for multiple business portfolios) look like CC.ENT.<id>.
  • A BSUID is unique per business portfolio and user. It changes if the user changes their phone number, but not when they change their username.
  • The phone number is omitted when the user has a username, your number hasn't messaged them in the last 30 days, and they aren't in your Meta-hosted contact book. Missing fields are omitted, never empty strings.

You don't need to do anything special to reply: pass the BSUID wherever you would pass a phone number.

EndpointField
POST /v1/messages/app/sendtoNumber (or to on v2)
POST /v1/messages/passthrough (legacy)to
POST /v1/messages/passthrough (v2)to or recipient
{
  "poolNumberId": "pn_abc123",
  "toNumber": "US.13491208655302741918",
  "text": "Thanks for reaching out!"
}

Pipes.bot sends BSUIDs to Meta in the recipient field and phone numbers in to. On the v2 passthrough the body is forwarded untouched, so set messaging_product and to or recipient yourself.

On the legacy API a user's identity is fromNumber, which is the phone number or, when there isn't one, the BSUID. If a phone number appears or disappears for the same user, their fromNumber changes and you get a new conversationId. API v2 stores no conversations, so you key your own records on the BSUID or wa_id in the event. For identity-change events, see BYON API v2 migration.

Asking for a phone number

If you need a phone number (for example to place a call or to send an authentication template), ask for it with REQUEST_CONTACT_INFO. The user chooses whether to share it.

As a template button. Add a REQUEST_CONTACT_INFO button when you create the template:

{
  "name": "ask_contact",
  "language": "en_US",
  "category": "UTILITY",
  "components": [
    { "type": "BODY", "text": "Share your phone number so we can follow up." },
    { "type": "BUTTONS", "buttons": [{ "type": "REQUEST_CONTACT_INFO" }] }
  ]
}

As an interactive message, inside the 24-hour service window:

{
  "poolNumberId": "pn_abc123",
  "messaging_product": "whatsapp",
  "recipient": "US.13491208655302741918",
  "type": "interactive",
  "interactive": {
    "type": "request_contact_info",
    "body": { "text": "Share your phone number so we can follow up." },
    "action": { "name": "request_contact_info" }
  }
}

Send it through POST /v1/messages/passthrough (or a v2 send frame). On API v2 Pipes.bot forwards the body unchanged. On legacy, put the BSUID in to instead of recipient.

The reply arrives as a normal inbound message with type: "contacts". When the user shared the info because you asked, contacts[].origin is contact_request (otherwise other):

{
  "type": "contacts",
  "contacts": [
    {
      "origin": "contact_request",
      "name": { "formatted_name": "Jane Doe" },
      "phones": [{ "phone": "+1 555-987-6543", "wa_id": "15559876543", "type": "CELL" }],
      "vcard": "QkVHSU46VkNBUkQKVkVSU0lPTjozLjAK..."
    }
  ]
}

Store the mapping from the BSUID (the message's from/from_user_id) to phones[].wa_id. phones[].phone is formatted for display; use wa_id as the number. vcard is a base64-encoded vCard of the same contact. See the full API v2 example payload.

Error 131062

Meta returns error 131062 when you send a one-tap, zero-tap or copy-code authentication template to a user whose phone number isn't known. These templates need a phone number. Ask for it with REQUEST_CONTACT_INFO first, then send the template to the shared number.

Complete example

Send a template with buttons, then handle the button reply:

const API_KEY = "ak_your_app_key";
const BASE = "https://api.pipes.bot";

// Send a template message
const res = await fetch(`${BASE}/v1/messages/passthrough`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    messaging_product: "whatsapp",
    type: "template",
    poolNumberId: "pn_abc123",
    to: "5511999999999",
    template: {
      name: "appointment_reminder",
      language: { code: "en_US" },
      components: [
        {
          type: "body",
          parameters: [
            { type: "text", text: "Dr. Smith" },
            { type: "text", text: "January 20 at 3:00 PM" },
          ],
        },
      ],
    },
  }),
});

const result = await res.json();
console.log("Sent template:", result.messages?.[0]?.id);

When the user replies to a button, you receive a webhook event:

{
  "type": "whatsapp_message",
  "data": {
    "type": "interactive",
    "messageId": "wamid.xyz...",
    "fromNumber": "5511999999999",
    "interactive": {
      "type": "button_reply",
      "button_reply": {
        "id": "confirm",
        "title": "Confirm"
      }
    }
  }
}

Then send a follow-up interactive message:

await fetch(`${BASE}/v1/messages/passthrough`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    messaging_product: "whatsapp",
    type: "interactive",
    poolNumberId: "pn_abc123",
    to: "5511999999999",
    interactive: {
      type: "button",
      body: {
        text: "Your appointment is confirmed! Would you like a reminder?",
      },
      action: {
        buttons: [
          { type: "reply", reply: { id: "remind_1h", title: "1 hour before" } },
          { type: "reply", reply: { id: "remind_1d", title: "1 day before" } },
          { type: "reply", reply: { id: "no_remind", title: "No thanks" } },
        ],
      },
    },
  }),
});

On this page