Understanding Templates
WhatsApp message templates are pre-approved message formats required by Meta for initiating conversations outside the 24-hour messaging window. This guide explains how templates work and how to manage them through the SendSeven API.
Meta's Template Library holds ready-made templates in many languages. Adopt one unchanged and it is created in your WABA already APPROVED — no review wait. Writing your own template (below) is the right choice when you need MARKETING templates or your own wording.
Required Scopes
| Scope | Purpose |
|---|---|
channels:read | List and view templates |
channels:admin | Create, edit, update, delete, and sync templates |
Template Structure
A template consists of components that define the message layout:
| Component | Description | Variables |
|---|---|---|
| HEADER | Top section: text, image, video, or document | Yes (text only) |
| BODY | Main message content | Yes ({{1}}, {{2}}, ... or {{name}}, {{order_number}}, ... -- see Parameter Formats) |
| FOOTER | Smaller text below the body | No |
| BUTTONS | URL buttons or quick replies | Yes (URL suffix) |
| CAROUSEL | A swipeable set of up to 10 cards, shown beneath a top-level BODY | Per card (see below) |
Example Template
HEADER: Order Confirmed
BODY: Hi {{1}}, your order #{{2}} has been confirmed. Estimated delivery: {{3}}.
FOOTER: Thank you for your purchase
BUTTONS: [Track Order -> https://example.com/track/{{1}}]
Carousel Templates
A carousel template pairs a single top-level BODY message with up to 10 cards that the recipient swipes through. Each card is a small template in its own right: an image or video header, an optional body text, and one or two buttons (at least one is required).
Meta enforces that all cards are structurally consistent — SendSeven validates this up front and returns a 400 before the template is ever submitted:
- Every card has exactly one
HEADER, and itsformatisIMAGEorVIDEO(neverTEXTorDOCUMENT). - All card headers use the same format (all
IMAGE, or allVIDEO). - A card
BODYis optional (a header-plus-buttons card is valid), but body presence must be consistent: either every card has aBODYor none do. - Every card has at least one button and at most two, and every card uses the same button layout: the same number of buttons, of the same types, in the same order.
BODY: Check out this week's featured deals, {{1}}!
CAROUSEL:
Card 0 HEADER(IMAGE) BODY "Summer jacket — now {{1}}" BUTTONS [Shop -> https://example.com/p/{{1}}]
Card 1 HEADER(IMAGE) BODY "Running shoes — now {{1}}" BUTTONS [Shop -> https://example.com/p/{{1}}]
Card 2 HEADER(IMAGE) BODY "Backpack — now {{1}}" BUTTONS [Shop -> https://example.com/p/{{1}}]
Each card's body placeholders are filled in per card at send time — see Send a Template Message.
Parameter Formats
A template's body can contain placeholders that are filled in at send time. Meta supports two parameter formats, and each template's format is chosen at creation time:
| Format | Placeholder syntax | Example |
|---|---|---|
positional | {{1}}, {{2}}, {{3}}, ... | Hi {{1}}, your order #{{2}} ships {{3}}. |
named | {{name}}, {{order_number}}, ... (letters, digits, underscores) | Hi {{name}}, your order #{{order_number}} ships {{eta}}. |
The format is chosen when the template is created -- either in WhatsApp Manager (Meta Business Suite) or via our POST /api/v1/whatsapp-templates endpoint. You can switch it later with an in-place edit; WhatsApp then reviews the template again, and its saved variable_mapping is cleared.
Every template returned by the API exposes its format on the top-level parameter_format field:
{
"id": "tpl_abc123",
"name": "order_confirmation",
"parameter_format": "named",
"...": "..."
}
Named templates require Meta Graph API v22.0 or higher. SendSeven uses v23.0, so all named templates created in WhatsApp Manager will sync correctly into SendSeven.
When sending a template, the shape of variable_values you pass must match the template's parameter_format. See Send a Template Message for full examples and the exact request shape for each format.
Template Categories
Meta classifies templates into categories that affect review time and per-message pricing:
| Category | Use Case | Examples |
|---|---|---|
UTILITY | Transaction-related | Order confirmations, shipping updates, appointment reminders |
MARKETING | Promotional | Offers, product announcements, newsletters |
AUTHENTICATION | Login/verification | OTP codes, two-factor authentication |
UTILITY templates are typically reviewed faster than MARKETING templates. AUTHENTICATION templates have specific format requirements set by Meta.
Template Statuses
| Status | Description |
|---|---|
APPROVED | Ready to use for sending messages |
PENDING | Submitted and awaiting Meta review |
REJECTED | Declined by Meta (see rejection reason) |
PAUSED | Paused by Meta because of quality issues (for example many blocks or negative feedback). Can't be sent until Meta lifts the pause; it can be edited |
DISABLED | Disabled by Meta |
Status changes reported by Meta (approval, rejection, pause, appeal, reinstatement) are applied within moments, so you rarely need to sync to see a new status. A template in appeal shows PENDING, a reinstated one APPROVED.
Language Support
Templates can have multiple language variants. Each variant is submitted to Meta separately and may have different approval statuses.
Common language codes:
| Code | Language |
|---|---|
en_US | English (US) |
en_GB | English (UK) |
de_DE | German |
fr_FR | French |
es_ES | Spanish |
pt_BR | Portuguese (Brazil) |
it_IT | Italian |
nl_NL | Dutch |
ar | Arabic |
When sending a template, the specified language variant must be in APPROVED status. If it is not, the API returns a 400 error.
Templates belong to the WhatsApp Business Account
A template is approved for a WhatsApp Business Account (WABA), not for one phone number. If several of your WhatsApp channels are numbers of the same account, they share one template set:
- Every channel of the account carries its own copy of each template (with its own
id), soGET /whatsapp-templates?channel_id=...works for any of them and template IDs you already stored keep working. - One sync refreshes every channel of the account.
- Creating, editing or deleting a template through SendSeven applies to every channel of the account. Saved settings that only exist in SendSeven (
variable_mapping,default_header_media_url,default_header_document_filename) are kept the same on every channel of the account. - You can send a template from any active channel of the account.
Use List WhatsApp Business Accounts to show one template list per account instead of one per number. The waba_id field on each template tells you which account it belongs to.
List WhatsApp Business Accounts
Returns your WhatsApp channels grouped by WhatsApp Business Account.
GET /api/v1/whatsapp-templates/wabas
Required Scope: channels:read
curl "https://api.sendseven.com/api/v1/whatsapp-templates/wabas" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
Response
{
"items": [
{
"waba_id": "104857392011223",
"representative_channel_id": "71a1351f-4d6a-4444-9c6b-1cc8d63d7408",
"channels": [
{"id": "5c0e8d2a-1b7f-4c3e-9a6d-2f4b8e1c7a90", "name": "Berlin", "identifier": "+4930123456", "is_active": false},
{"id": "71a1351f-4d6a-4444-9c6b-1cc8d63d7408", "name": "Munich", "identifier": "+4989123456", "is_active": true}
]
}
]
}
| Field | Description |
|---|---|
waba_id | WhatsApp Business Account ID. null when the account of a channel can't be determined; that channel then forms its own group. |
representative_channel_id | The channel to use when listing or syncing this account's templates: the first active channel (by name), or the first channel when none is active. |
channels | The account's channels, sorted by name. Archived channels are not included. |
Groups are sorted by the name of their first channel. The list is not paginated.
List Templates
Retrieve all templates for your WhatsApp Business Account.
GET /api/v1/whatsapp-templates
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
channel_id | string | -- | Filter by WhatsApp channel (UUID). Also triggers an auto-sync from Meta when that channel has pending templates |
status | string | -- | Filter: APPROVED, PENDING, REJECTED, DISABLED |
limit | integer | 100 | Max results to return (1--500) |
offset | integer | 0 | Number of results to skip |
This endpoint uses limit/offset and returns a templates array with a total count -- not the items + pagination envelope used by conversations, contacts, and most other list endpoints. Each language variant of a template is its own row, keyed by the top-level language field.
curl
curl -X GET "https://api.sendseven.com/api/v1/whatsapp-templates?status=APPROVED&limit=50" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
Response
{
"templates": [
{
"id": "tpl_abc123",
"tenant_id": "9f3a1c7e-5b2d-4e8a-91c4-7d6f0b3e2a15",
"channel_id": "71a1351f-4d6a-4444-9c6b-1cc8d63d7408",
"waba_id": "109876543210987",
"template_id": "1234567890123456",
"name": "order_confirmation",
"language": "en_US",
"status": "APPROVED",
"category": "UTILITY",
"quality_score": "GREEN",
"parameter_format": "positional",
"header_type": "TEXT",
"header_text": "Order Confirmed",
"body_text": "Hi {{1}}, your order #{{2}} has been confirmed. Estimated delivery: {{3}}.",
"footer_text": "Thank you for your purchase",
"buttons": [
{
"type": "URL",
"text": "Track Order",
"url": "https://example.com/track/{{1}}",
"phone_number": null,
"example": null
}
],
"quick_reply_buttons": [],
"variable_mapping": {
"1": "contact.first_name",
"2": "custom_field.company"
},
"default_header_media_url": null,
"default_header_document_filename": null,
"edit_limits": {
"editable": true,
"not_editable_reason": null,
"category_locked": true,
"edits_used_30d": 0,
"edits_remaining_30d": 10,
"next_edit_available_at": null,
"last_edited_at": null
},
"last_synced_at": "2026-01-20T09:55:00Z",
"created_at": "2026-01-20T10:00:00Z",
"updated_at": "2026-01-20T10:00:00Z"
}
],
"total": 12,
"channel_id": null,
"status_filter": "APPROVED"
}
channel_id and status_filter echo the filters you sent, so a client can tell which slice of templates it is looking at.
Get Template Details
GET /api/v1/whatsapp-templates/{template_id}
Required Scope: channels:read
Returns a single template object with the same flat shape as the entries in the list response above.
curl -X GET "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
Create Template
Submits a new template to Meta for approval. Review typically takes 24--48 hours; the template starts in PENDING.
To skip the review entirely, adopt a pre-approved entry from Meta's Template Library instead — those are created as APPROVED as long as the wording is unchanged.
POST /api/v1/whatsapp-templates
Required Scope: channels:admin
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
channel_id | string | Yes | The WhatsApp channel (and therefore WABA) to create the template on |
name | string | Yes | Lowercase letters, digits and underscores only (^[a-z0-9_]+$), max 512 chars |
category | string | Yes | UTILITY, MARKETING, or AUTHENTICATION |
language | string | No | ISO language code, default en |
components | array | Yes | The template layout — see below |
parameter_format | string | No | positional (default) or named. Can be switched later with an edit |
body_named_examples | object | No | Required when parameter_format is named: example value per placeholder name |
allow_category_change | boolean | No | Default true — lets Meta reclassify the template rather than rejecting it |
default_header_media_url | string | No | Fallback header media for IMAGE/VIDEO/DOCUMENT templates, used when a send call omits header_media_url. Typically the URL returned by POST /whatsapp-templates/upload-media |
default_header_document_filename | string | No | Filename shown to the recipient for DOCUMENT headers (include the extension, e.g. invoice.pdf) |
Each entry in components has type (HEADER, BODY, FOOTER, BUTTONS, CAROUSEL), plus format for headers, text for text content, example for sample values, and buttons for the buttons component.
A CAROUSEL component adds one more field: cards, an array of up to 10 card objects. Each card is { "components": [ ... ] }, holding the card's own HEADER (format IMAGE or VIDEO), an optional BODY, and a required BUTTONS component (1-2 buttons). All cards must be structurally consistent (see Carousel Templates); SendSeven returns a 400 up front if they are not.
upload-media call per cardEach carousel card needs its own header media. Call POST /whatsapp-templates/upload-media once per card, then put that card's returned handle in its HEADER component's example.header_handle ({ "header_handle": ["<handle>"] }) — exactly as you would for a single media-header template, but repeated per card.
curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "a1b2c3d4-5678-9abc-def0-1234567890ab",
"name": "order_confirmation",
"category": "UTILITY",
"language": "en",
"parameter_format": "positional",
"components": [
{ "type": "HEADER", "format": "TEXT", "text": "Order Update" },
{
"type": "BODY",
"text": "Hi {{1}}, your order #{{2}} has been shipped! Estimated delivery: {{3}}.",
"example": { "body_text": [["John", "12345", "March 5"]] }
},
{ "type": "FOOTER", "text": "Reply STOP to unsubscribe" },
{
"type": "BUTTONS",
"buttons": [
{ "type": "URL", "text": "Track Order", "url": "https://example.com/track/{{1}}" }
]
}
],
"allow_category_change": true
}'
Response (201 Created)
{
"success": true,
"id": "tpl_abc123",
"template_id": "1234567890123456",
"status": "PENDING",
"error": null
}
Create errors
When the template can't be created, the request fails with an HTTP error. detail is an object with a readable message and a code:
| Status | code | Meaning |
|---|---|---|
| 400 | TEMPLATE_CREATE_FAILED | The request failed before it reached WhatsApp, for example the channel or its WhatsApp Business Account could not be found |
| 422 | WHATSAPP_TEMPLATE_REJECTED | WhatsApp refused the template. message says what to change |
| 429 | WHATSAPP_TEMPLATE_REJECTED | WhatsApp is rate-limiting your WhatsApp Business Account. Retry after the seconds in the Retry-After header (60) |
| 503 | WHATSAPP_TEMPLATE_REJECTED | WhatsApp had a temporary problem. Retry after the seconds in the Retry-After header (30) |
When WhatsApp answered, detail also has provider: "meta", meta_code, meta_subcode, category, is_retryable, meta_message, meta_details, meta_user_title, meta_user_message and fbtrace_id. Quote the fbtrace_id if you contact support.
{
"detail": {
"message": "The body text contains a variable at the start or end. Add text before {{1}}.",
"code": "WHATSAPP_TEMPLATE_REJECTED",
"provider": "meta",
"meta_code": 100,
"meta_subcode": 2388299,
"category": "template_creation",
"is_retryable": false,
"fbtrace_id": "AbCdEfGh123"
}
}
For a named template, drop example.body_text from the BODY component and send body_named_examples instead:
{
"parameter_format": "named",
"components": [
{ "type": "BODY", "text": "Hi {{first_name}}, order #{{order_id}} has shipped." }
],
"body_named_examples": { "first_name": "John", "order_id": "ORD-123" }
}
Create a carousel template
Provide a top-level BODY, then a CAROUSEL component whose cards array holds the structurally-identical cards. Each card's HEADER carries its own uploaded media handle in example.header_handle.
curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "a1b2c3d4-5678-9abc-def0-1234567890ab",
"name": "weekly_deals",
"category": "MARKETING",
"language": "en",
"parameter_format": "positional",
"components": [
{
"type": "BODY",
"text": "Check out this week'\''s featured deals, {{1}}!",
"example": { "body_text": [["Alex"]] }
},
{
"type": "CAROUSEL",
"cards": [
{
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["4::aW1hZ2U...card0"] } },
{ "type": "BODY", "text": "Summer jacket — now {{1}}", "example": { "body_text": [["-30%"]] } },
{ "type": "BUTTONS", "buttons": [
{ "type": "URL", "text": "Shop", "url": "https://example.com/p/{{1}}" }
] }
]
},
{
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["4::aW1hZ2U...card1"] } },
{ "type": "BODY", "text": "Running shoes — now {{1}}", "example": { "body_text": [["-20%"]] } },
{ "type": "BUTTONS", "buttons": [
{ "type": "URL", "text": "Shop", "url": "https://example.com/p/{{1}}" }
] }
]
}
]
}
]
}'
Each card was uploaded separately: two POST /whatsapp-templates/upload-media calls produced the two header_handle values above. The two cards share an identical button layout (one URL button), as Meta requires.
Edit Template
POST /api/v1/whatsapp-templates/{template_id}/edit
Required Scope: channels:admin
Changes an existing template's content in place and submits it to WhatsApp for review again. The template keeps its id, name and language, so campaigns, flows and schedules that reference it keep working once it is approved.
WhatsApp's rules:
| Status | Editable | Limits |
|---|---|---|
APPROVED | Yes | 1 edit per 24 hours, 10 edits in a rolling 30 days, category locked |
REJECTED, PAUSED | Yes | Unlimited; the category can change |
PENDING, DISABLED | No |
Name and language can't change, and each language is a separate template that you edit on its own. Every template object carries edit_limits (editable, not_editable_reason, category_locked, edits_used_30d, edits_remaining_30d, next_edit_available_at, last_edited_at), so check it before you submit. edits_remaining_30d is null when the template has no limit.
The body takes the same content fields as Create Template (category, components, parameter_format, body_named_examples, message_send_ttl_seconds, default_header_media_url, default_header_document_filename). WhatsApp replaces all components, so send the complete template, not only the changed part. Sending channel_id, name or language returns 422 template_identity_immutable. For a media header, pass a fresh example.header_handle from POST /whatsapp-templates/upload-media; without one, SendSeven re-submits the saved default header media, or answers 422 header_media_required.
curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/edit" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"category": "UTILITY",
"components": [
{"type": "HEADER", "format": "TEXT", "text": "Order Confirmed"},
{"type": "BODY", "text": "Hi {{1}}, your order #{{2}} ships today.",
"example": {"body_text": [["John", "12345"]]}},
{"type": "FOOTER", "text": "Thank you for your purchase"}
]
}'
{
"success": true,
"template": { "id": "tpl_abc123", "status": "PENDING", "...": "..." },
"removed_variable_mapping_keys": [],
"meta_status": "PENDING"
}
After the edit the template is back in review (PENDING) and can't be sent until WhatsApp approves it again. The template's own variable_mapping keeps the placeholders that still exist; the cleared ones are listed in removed_variable_mapping_keys. Switching parameter_format clears the whole mapping. Mappings stored on campaigns, flows or scheduled sends are not changed, so check Template Usage first.
Errors return detail as an object with message and error_code:
| Status | error_code |
|---|---|
| 400 | template_channel_unavailable, or an invalid carousel structure |
| 404 | template_not_found |
| 409 | template_not_editable_status (detail.status holds the current status) |
| 422 | template_identity_immutable, template_category_locked, template_edit_unsupported, template_invalid, header_media_required, template_edit_rejected |
| 429 | template_edit_limit_reached (detail.next_edit_available_at, Retry-After when known), meta_rate_limited (Retry-After) |
| 503 | meta_unavailable (Retry-After) |
When WhatsApp itself refused the edit, detail also has provider: "meta", meta_code, meta_subcode, meta_message, meta_details, is_retryable and fbtrace_id.
Template Usage
GET /api/v1/whatsapp-templates/{template_id}/usage
Required Scope: channels:read
Lists where a template is used, so you can see what an edit affects: up to 20 unfinished campaigns (id, name, status) with campaigns_count, up to 20 flows (id, name, is_active) with flows_count, plus scheduled_sends_count, scheduler_rules_count and in_use. The campaigns list needs campaigns:read and the flows list needs flows:read; without the matching scope that list is empty while its count is still returned. Integrations that send templates by name can't be listed. Returns 404 for an unknown template.
Update Template
PATCH /api/v1/whatsapp-templates/{template_id}
Required Scope: channels:admin
Changes only the settings SendSeven stores for the template, which WhatsApp doesn't review: default_header_media_url and default_header_document_filename. To change the content (header, body, footer, buttons, carousel cards, category), use Edit Template.
curl -X PATCH "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{"default_header_media_url": "https://cdn.example.com/header.jpg"}'
Returns the updated template object.
default_header_media_url (here and on Create Template) must be an external https:// URL or point to an attachment of your workspace (attachment ID, its download URL or the URL returned by POST /whatsapp-templates/upload-media). Another workspace's attachment returns 404 "Attachment {id} not found" and nothing is changed.
Delete Template
DELETE /api/v1/whatsapp-templates/{template_id}
Required Scope: channels:admin
Deletes the template from both Meta and SendSeven and returns 204 No Content with an empty body. An approved template becomes unavailable for sending immediately.
curl -X DELETE "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
Sync Templates from Meta
If you create or modify templates directly in the Meta Business Manager, sync them to SendSeven. channel_id is a required query parameter. Templates belong to the WhatsApp Business Account, so one sync refreshes every channel of that account in your workspace; you don't need to sync each number.
POST /api/v1/whatsapp-templates/sync?channel_id={channel_id}
Required Scope: channels:admin
curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/sync?channel_id=71a1351f-4d6a-4444-9c6b-1cc8d63d7408" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
Response
{
"synced": 15,
"added": 2,
"updated": 3,
"removed": 0,
"errors": [],
"channel_ids": [
"71a1351f-4d6a-4444-9c6b-1cc8d63d7408",
"5c0e8d2a-1b7f-4c3e-9a6d-2f4b8e1c7a90"
]
}
removed counts templates that no longer exist in WhatsApp and were dropped locally. synced, added, updated and removed describe the channel you passed. channel_ids lists every channel that was refreshed: the one you passed first, then the other channels of the same WhatsApp Business Account.
Carousel templates sync just like any other. A carousel authored in WhatsApp Manager comes back with its cards intact on the carousel_cards field of the template object (an ordered list of { "components": [...] } cards); carousel_cards is null for non-carousel templates.
Listing templates with channel_id syncs automatically when that channel has PENDING templates, at most once every 5 minutes per WhatsApp Business Account. A manual sync always asks WhatsApp and resets that 5-minute window for every channel of the account. If WhatsApp is rate-limiting template requests for your account, this endpoint returns 429 Too Many Requests with detail.code template_sync_rate_limited, detail.retry_after_seconds, and a Retry-After header when WhatsApp gives one. Nothing is changed in that case.
Next Steps
- Meta Template Library -- adopt pre-approved templates with instant approval
- Send a Template Message -- sending templates with variable values
- Getting Started with WhatsApp -- WhatsApp integration overview
- 24-Hour Messaging Window -- when templates are required