Pipes.bot
BYON

Template Management

Create, read, update, and delete WhatsApp message templates for your BYON numbers.

Message templates are pre-approved message formats that let you send outbound messages outside the 24-hour service window. Templates must be submitted to Meta for review before they can be used.

Template management is available exclusively for BYON numbers using ak_ API keys.

Base URL

All template endpoints are scoped to a BYON number:

/v1/pool-numbers/:poolNumberId/templates
Authorization: Bearer YOUR_APP_API_KEY

List templates

Retrieve all templates registered on your WABA (WhatsApp Business Account).

curl https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates \
  -H "Authorization: Bearer ak_your_app_key"

You can filter by status or limit results using query parameters, which are forwarded directly to Meta:

curl "https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates?status=APPROVED&limit=10" \
  -H "Authorization: Bearer ak_your_app_key"

Response

{
  "data": [
    {
      "name": "order_confirmation",
      "status": "APPROVED",
      "category": "TRANSACTIONAL",
      "language": "en_US",
      "id": "123456789",
      "components": [
        { "type": "BODY", "text": "Your order {{1}} has shipped. Track it here: {{2}}" }
      ]
    }
  ],
  "paging": {
    "cursors": { "before": "...", "after": "..." }
  }
}

Get template by name

Retrieve a specific template by name. Returns all language variants.

curl https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates/order_confirmation \
  -H "Authorization: Bearer ak_your_app_key"

Create a template

Submit a new template for Meta review. Templates start in PENDING status and move to APPROVED or REJECTED after review.

curl -X POST https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_confirmation",
    "category": "TRANSACTIONAL",
    "language": "en_US",
    "components": [
      {
        "type": "HEADER",
        "format": "TEXT",
        "text": "Order Update"
      },
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order {{2}} has been confirmed. Estimated delivery: {{3}}."
      },
      {
        "type": "FOOTER",
        "text": "Thank you for shopping with us"
      },
      {
        "type": "BUTTONS",
        "buttons": [
          { "type": "URL", "text": "Track Order", "url": "https://example.com/track/{{1}}" },
          { "type": "PHONE_NUMBER", "text": "Call Support", "phone_number": "+15551234567" }
        ]
      }
    ]
  }'

Response

{
  "id": "123456789",
  "status": "PENDING",
  "category": "TRANSACTIONAL"
}

Update a template

Modify an existing template's components. The template id (Meta's numeric ID) is required in the body — retrieve it with the GET endpoint first. Updated templates are resubmitted for review.

curl -X POST https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates/order_confirmation \
  -H "Authorization: Bearer ak_your_app_key" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "123456789",
    "components": [
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order {{2}} is confirmed. Delivery by {{3}}. Questions? Reply here."
      }
    ]
  }'

After an update, the template status resets to IN_REVIEW until Meta re-approves it.

Delete a template

Delete a template by name. To delete a specific language variant, include the hsm_id query parameter.

# Delete all variants
curl -X DELETE https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates/order_confirmation \
  -H "Authorization: Bearer ak_your_app_key"

# Delete a specific language variant
curl -X DELETE "https://api.pipes.bot/v1/pool-numbers/pn_abc123/templates/order_confirmation?hsm_id=123456789" \
  -H "Authorization: Bearer ak_your_app_key"

Response

{ "success": true }

Deleted template names are reserved by Meta for 30 days and cannot be reused during that period.

Template components

Each template consists of up to four component types:

HEADER (optional)

Displayed above the body. Supports text or media.

FormatNotes
TEXTMax 60 characters, 1 variable allowed ({{1}})
IMAGEUse a media handle from the upload endpoint
VIDEOUse a media handle from the upload endpoint
DOCUMENTUse a media handle from the upload endpoint

For media headers, upload the file first to get a handle:

# Upload media for template header
curl -X POST https://api.pipes.bot/v1/media/upload/meta \
  -H "Authorization: Bearer ak_your_app_key" \
  -F "file=@header-image.jpg" \
  -F "poolNumberId=pn_abc123"

Response: { "handle": "h:abc123..." }

Then reference the handle in the header component:

{
  "type": "HEADER",
  "format": "IMAGE",
  "example": { "header_handle": ["h:abc123..."] }
}

BODY (required)

The main message content. Max 1024 characters. Variables use {{1}}, {{2}}, etc. for dynamic parameters filled at send time.

{
  "type": "BODY",
  "text": "Hi {{1}}, your appointment is on {{2}} at {{3}}."
}

Short text below the body. Max 60 characters, no variables allowed.

{
  "type": "FOOTER",
  "text": "Reply STOP to unsubscribe"
}

BUTTONS (optional)

Up to 10 buttons total. Supported button types:

TypeMax countDescription
QUICK_REPLY10Simple text reply buttons
URL2Link buttons, supports 1 variable
PHONE_NUMBER1Call button with E.164 phone number
COPY_CODE1Copy a code to clipboard (OTP templates)
{
  "type": "BUTTONS",
  "buttons": [
    { "type": "QUICK_REPLY", "text": "Confirm" },
    { "type": "QUICK_REPLY", "text": "Cancel" },
    { "type": "URL", "text": "View Details", "url": "https://example.com/order/{{1}}" }
  ]
}

Template categories

CategoryUse case
MARKETINGPromotions, offers, newsletters
TRANSACTIONALOrder updates, receipts, shipping notifications
OTPOne-time passwords and verification codes

Template status lifecycle

StatusCan send?Description
PENDINGNoSubmitted, awaiting Meta review
IN_REVIEWNoUnder review (new or edited template)
APPROVEDYesApproved and ready to send
ACTIVE_HIGH_QUALITYYesApproved with strong quality signals
ACTIVE_QUALITY_PENDINGYesApproved, quality signals pending
REJECTEDNoFailed Meta review
PAUSEDNoTemporarily paused due to quality issues
DISABLEDNoPermanently disabled, cannot be restored

Template status webhooks

When a template's status changes, Meta sends a webhook event that Pipes.bot forwards to your App's webhook URL:

{
  "type": "template_status_update",
  "poolNumberId": "pn_abc123",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "event": "APPROVED",
    "message_template_name": "order_confirmation",
    "message_template_id": "123456789",
    "message_template_language": "en_US"
  }
}

Naming rules

  • Alphanumeric characters and underscores only
  • Max 512 characters
  • Must be unique within your WABA
  • Deleted names are reserved for 30 days

Limits

  • Max 250 templates per WABA
  • Max 25 MB per media file (for media header uploads)

Error reference

StatusCodeDescription
400INVALID_REQUESTMissing or malformed request body
401UNAUTHORIZEDMissing or invalid API key
403BYON_ONLYTemplate management requires an ak_ key
403FORBIDDENNumber does not belong to your App
403INSUFFICIENT_SCOPEAccess token missing whatsapp_business_management
404POOL_NUMBER_NOT_FOUNDPool number ID not found
404NOT_BYON_NUMBERNumber is not a BYON number
500NO_CREDENTIALSBYON number has no access token configured
500WABA_NOT_CONFIGUREDBYON number has no WABA ID configured
502META_UNREACHABLECould not reach Meta's API

Complete example

Create a marketing template with an image header and two buttons:

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

// Step 1: Upload header image
const form = new FormData();
form.append("file", imageFile);
form.append("poolNumberId", POOL_NUMBER_ID);

const uploadRes = await fetch(`${BASE}/v1/media/upload/meta`, {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}` },
  body: form,
});
const { handle } = await uploadRes.json();

// Step 2: Create template
const templateRes = await fetch(
  `${BASE}/v1/pool-numbers/${POOL_NUMBER_ID}/templates`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "summer_sale",
      category: "MARKETING",
      language: "en_US",
      components: [
        {
          type: "HEADER",
          format: "IMAGE",
          example: { header_handle: [handle] },
        },
        {
          type: "BODY",
          text: "Hi {{1}}, our summer sale is live! Get {{2}} off your next order.",
        },
        {
          type: "FOOTER",
          text: "Limited time offer",
        },
        {
          type: "BUTTONS",
          buttons: [
            { type: "URL", text: "Shop Now", url: "https://example.com/sale" },
            { type: "QUICK_REPLY", text: "Not interested" },
          ],
        },
      ],
    }),
  },
);

const template = await templateRes.json();
console.log("Template created:", template.id, template.status);

On this page