API v2 Example Payloads
Ready-to-use API v2 webhook envelopes (fixtures) for testing your handler, with a script that signs and replays them.
These are complete API v2 envelopes, exactly as Pipes.bot delivers them to your webhook and inside WebSocket event frames. Use them as test fixtures for your handler, or replay them against your local server to check signature verification and deduplication.
Every fixture is available as a file at https://docs.pipes.bot/fixtures/byon-v2/<name>.json. The IDs (app_abc123, pn_abc123, wamid..., US.1349...) are examples.
Using the fixtures
In tests
Download the files you need and feed them to your handler:
mkdir -p test/fixtures && cd test/fixtures
for name in message-text message-text-username-with-phone message-text-bsuid-only message-text-parent-bsuid message-image message-contact-request status-delivered-bsuid status-failed system-user-changed-number system-user-changed-user-id template-status-update; do
curl -sO https://docs.pipes.bot/fixtures/byon-v2/$name.json
doneimport envelope from "./fixtures/message-text-bsuid-only.json";
test("keys the user on the BSUID when there is no phone number", async () => {
const result = await handleEnvelope(envelope);
expect(result.userKey).toBe("US.13491208655302741918");
});Replaying against your server
This script fetches a fixture, signs it with Standard Webhooks and POSTs it to your endpoint, the same way Pipes.bot does. Run it with your App's signing secret (whsec_...) or with a test secret your local server is configured to use. Node.js 18 or later, no dependencies.
// Usage: node replay.mjs <fixture-name> <webhook-url> <whsec_secret>
import crypto from "node:crypto";
const [name, url, secret] = process.argv.slice(2);
if (!name || !url || !secret) {
console.error("Usage: node replay.mjs <fixture-name> <webhook-url> <whsec_secret>");
process.exit(1);
}
const res = await fetch(`https://docs.pipes.bot/fixtures/byon-v2/${name}.json`);
if (!res.ok) throw new Error(`Unknown fixture: ${name}`);
const body = JSON.stringify(await res.json()); // the exact bytes that get signed and sent
const id = JSON.parse(body).id; // on v2, webhook-id equals the envelope id
const timestamp = Math.floor(Date.now() / 1000).toString();
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const signature = crypto
.createHmac("sha256", key)
.update(`${id}.${timestamp}.${body}`)
.digest("base64");
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"webhook-id": id,
"webhook-timestamp": timestamp,
"webhook-signature": `v1,${signature}`,
},
body,
});
console.log(response.status, await response.text());node replay.mjs message-text http://localhost:3000/webhook whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSwRun the same fixture twice to check deduplication: the envelope id doesn't change, so your handler should process it once.
Messages
Text message
A user without a username. Meta shares the phone number (from, wa_id) and also sends the BSUID (from_user_id, user_id).
{
"id": "evt_0f1e2d3c4b5a69788796a5b4c3d2e1f0",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson"
},
"wa_id": "16505551234",
"user_id": "US.13491208655302741918"
}
],
"messages": [
{
"from": "16505551234",
"from_user_id": "US.13491208655302741918",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQTRBNjU5OUFFRTAzODEwMTQ0RgA=",
"timestamp": "1749416383",
"type": "text",
"text": {
"body": "Does it come in another color?"
}
}
]
}
}Text from a user with a username (phone shared)
The user has a username, but the phone number is still shared, for example because your number messaged them in the last 30 days. contacts[].profile.username is present.
message-text-username-with-phone.json
{
"id": "evt_1a2b3c4d5e6f708192a3b4c5d6e7f801",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson",
"username": "@realsheenanelson"
},
"wa_id": "16505551234",
"user_id": "US.13491208655302741918"
}
],
"messages": [
{
"from": "16505551234",
"from_user_id": "US.13491208655302741918",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQTRBNjU5OUFFRTAzODEwMTQ0RgA=",
"timestamp": "1749416383",
"type": "text",
"text": {
"body": "Does it come in another color?"
}
}
]
}
}Text from a user with a username (no phone)
The phone number is hidden. messages[].from and contacts[].wa_id are omitted; identify the user by from_user_id. Reply by sending to the BSUID.
{
"id": "evt_2b3c4d5e6f708192a3b4c5d6e7f80912",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson",
"username": "@realsheenanelson"
},
"user_id": "US.13491208655302741918"
}
],
"messages": [
{
"from_user_id": "US.13491208655302741918",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQTRBNjU5OUFFRTAzODEwMTQ0RgA=",
"timestamp": "1749416383",
"type": "text",
"text": {
"body": "Does it come in another color?"
}
}
]
}
}Text with a parent BSUID
Parent BSUIDs (CC.ENT.<id>) are opt-in, for businesses with multiple portfolios. When enabled, from_parent_user_id and contacts[].parent_user_id are added.
message-text-parent-bsuid.json
{
"id": "evt_3c4d5e6f708192a3b4c5d6e7f8091a2b",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson",
"username": "@realsheenanelson"
},
"user_id": "US.13491208655302741918",
"parent_user_id": "US.ENT.11815799212886844830"
}
],
"messages": [
{
"from_user_id": "US.13491208655302741918",
"from_parent_user_id": "US.ENT.11815799212886844830",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQTRBNjU5OUFFRTAzODEwMTQ0RgA=",
"timestamp": "1749416383",
"type": "text",
"text": {
"body": "Does it come in another color?"
}
}
]
}
}Image message
Media is downloaded and stored before delivery. pipes.media is keyed by Meta's media ID (messages[].image.id). If a download fails, the entry is { "unavailable": true } and the event is still delivered.
{
"id": "evt_4d5e6f708192a3b4c5d6e7f8091a2b3c",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {
"1003383421387256": {
"mediaId": "aBcDeFgHiJkLmNoPqRs1t",
"downloadUrl": "/v1/media/download/aBcDeFgHiJkLmNoPqRs1t",
"mimeType": "image/jpeg",
"byteSize": 48213
}
}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson"
},
"wa_id": "16505551234",
"user_id": "US.13491208655302741918"
}
],
"messages": [
{
"from": "16505551234",
"from_user_id": "US.13491208655302741918",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQ1QjhGQzE0MjE5RDZBRTlBMjMA",
"timestamp": "1749416383",
"type": "image",
"image": {
"caption": "Do you have this one in blue?",
"mime_type": "image/jpeg",
"sha256": "N4Ts1d9cU0tQdPZ2r8uC5n3o2kE8tHn6yq3tJjv0wTU=",
"id": "1003383421387256"
}
}
]
}
}Phone number shared after a contact request
The reply to a REQUEST_CONTACT_INFO button or interactive message. origin is contact_request. Store the mapping from from_user_id to phones[].wa_id. vcard is a base64-encoded vCard. See Asking for a phone number.
{
"id": "evt_5e6f708192a3b4c5d6e7f8091a2b3c4d",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Sheena Nelson",
"username": "@realsheenanelson"
},
"user_id": "US.13491208655302741918"
}
],
"messages": [
{
"from_user_id": "US.13491208655302741918",
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQ2QzlBRDI1MzBFN0JGMEFCMzQA",
"timestamp": "1749416383",
"type": "contacts",
"contacts": [
{
"origin": "contact_request",
"name": {
"formatted_name": "Sheena Nelson",
"first_name": "Sheena",
"last_name": "Nelson"
},
"phones": [
{
"phone": "+1 650-555-1234",
"wa_id": "16505551234",
"type": "CELL"
}
],
"vcard": "QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpOZWxzb247U2hlZW5hOzs7CkZOOlNoZWVuYSBOZWxzb24KVEVMO3R5cGU9Q0VMTDt0eXBlPVZPSUNFO3dhaWQ9MTY1MDU1NTEyMzQ6KzEgNjUwLTU1NS0xMjM0CkVORDpWQ0FSRA=="
}
]
}
]
}
}Statuses
Delivered status for a BSUID recipient
Statuses arrive under field: "messages" in whatsapp.statuses[]. recipient_user_id and a contacts array are included. For a recipient without a shared phone number, recipient_id and contacts[].wa_id are omitted.
{
"id": "evt_6f708192a3b4c5d6e7f8091a2b3c4d5e",
"field": "messages",
"createdAt": "2025-06-15T23:27:54.031Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"user_id": "US.13491208655302741918",
"parent_user_id": "US.ENT.11815799212886844830"
}
],
"statuses": [
{
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQUFERjg0NDEzNDdFODU3MUMxMAA=",
"status": "delivered",
"timestamp": "1750030073",
"recipient_user_id": "US.13491208655302741918",
"recipient_parent_user_id": "US.ENT.11815799212886844830",
"pricing": {
"billable": true,
"pricing_model": "PMP",
"type": "regular",
"category": "marketing"
}
}
]
}
}Failed status
Meta's errors[] array is passed through untouched, so you can act on Meta's numeric code.
{
"id": "evt_708192a3b4c5d6e7f8091a2b3c4d5e6f",
"field": "messages",
"createdAt": "2025-06-15T23:27:54.031Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"statuses": [
{
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQUFERjg0NDEzNDdFODU3MUMxMAA=",
"status": "failed",
"timestamp": "1750030073",
"recipient_id": "16505551234",
"errors": [
{
"code": 131049,
"title": "This message was not delivered to maintain healthy ecosystem engagement.",
"message": "This message was not delivered to maintain healthy ecosystem engagement.",
"error_data": {
"details": "In order to maintain a healthy ecosystem engagement, the message failed to be delivered."
},
"href": "/documentation/business-messaging/whatsapp/support/error-codes"
}
]
}
]
}
}Identity changes
User changed their phone number
Changing the phone number also regenerates the BSUID: system.previous_user_id is the old BSUID, system.user_id the new one and system.wa_id the new phone number.
system-user-changed-number.json
{
"id": "evt_8192a3b4c5d6e7f8091a2b3c4d5e6f70",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"messages": [
{
"from": "16505559999",
"id": "wamid.HBgLMTY1MDU1NTk5OTkVAgASGBQ3RDBCRTM2NDFGOENBMUJDNDUA",
"timestamp": "1749416383",
"type": "system",
"system": {
"body": "User A changed from 16505551234 to 16505559999",
"wa_id": "16505559999",
"user_id": "US.99999999999999999999",
"previous_user_id": "US.13491208655302741918",
"type": "user_changed_number"
}
}
]
}
}User's BSUID was regenerated
Move your records from system.previous_user_id to system.user_id.
system-user-changed-user-id.json
{
"id": "evt_92a3b4c5d6e7f8091a2b3c4d5e6f7081",
"field": "messages",
"createdAt": "2025-06-08T20:59:44.512Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001111",
"phone_number_id": "106540352242922"
},
"messages": [
{
"id": "wamid.HBgLMTY1MDU1NTk5OTkVAgASGBQ4RTFDRjQ3NTJBOURCMkNENTYA",
"timestamp": "1749416383",
"type": "system",
"system": {
"body": "User changed from US.13491208655302741918 to US.99999999999999999999",
"user_id": "US.99999999999999999999",
"previous_user_id": "US.13491208655302741918",
"type": "user_changed_user_id"
}
}
]
}
}Templates
Template status update
Every Meta webhook field for your numbers is delivered, not only messages. Here whatsapp is Meta's template status change.value. pipes.media is always {}.
{
"id": "evt_a3b4c5d6e7f8091a2b3c4d5e6f708192",
"field": "message_template_status_update",
"createdAt": "2025-06-10T14:02:11.870Z",
"pipes": {
"appId": "app_abc123",
"poolNumberId": "pn_abc123",
"media": {}
},
"whatsapp": {
"event": "APPROVED",
"message_template_id": 1234567890123456,
"message_template_name": "order_shipped",
"message_template_language": "en_US",
"reason": null,
"message_template_category": "UTILITY"
}
}