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_KEYList 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.
| Format | Notes |
|---|---|
TEXT | Max 60 characters, 1 variable allowed ({{1}}) |
IMAGE | Use a media handle from the upload endpoint |
VIDEO | Use a media handle from the upload endpoint |
DOCUMENT | Use 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}}."
}FOOTER (optional)
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:
| Type | Max count | Description |
|---|---|---|
QUICK_REPLY | 10 | Simple text reply buttons |
URL | 2 | Link buttons, supports 1 variable |
PHONE_NUMBER | 1 | Call button with E.164 phone number |
COPY_CODE | 1 | Copy 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
| Category | Use case |
|---|---|
MARKETING | Promotions, offers, newsletters |
TRANSACTIONAL | Order updates, receipts, shipping notifications |
OTP | One-time passwords and verification codes |
Template status lifecycle
| Status | Can send? | Description |
|---|---|---|
PENDING | No | Submitted, awaiting Meta review |
IN_REVIEW | No | Under review (new or edited template) |
APPROVED | Yes | Approved and ready to send |
ACTIVE_HIGH_QUALITY | Yes | Approved with strong quality signals |
ACTIVE_QUALITY_PENDING | Yes | Approved, quality signals pending |
REJECTED | No | Failed Meta review |
PAUSED | No | Temporarily paused due to quality issues |
DISABLED | No | Permanently 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
| Status | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | Missing or malformed request body |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | BYON_ONLY | Template management requires an ak_ key |
403 | FORBIDDEN | Number does not belong to your App |
403 | INSUFFICIENT_SCOPE | Access token missing whatsapp_business_management |
404 | POOL_NUMBER_NOT_FOUND | Pool number ID not found |
404 | NOT_BYON_NUMBER | Number is not a BYON number |
500 | NO_CREDENTIALS | BYON number has no access token configured |
500 | WABA_NOT_CONFIGURED | BYON number has no WABA ID configured |
502 | META_UNREACHABLE | Could 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);