Skip to main content

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.

Need an approved template right now?

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​

ScopePurpose
channels:readList and view templates
channels:adminCreate, edit, update, delete, and sync templates

Template Structure​

A template consists of components that define the message layout:

ComponentDescriptionVariables
HEADERTop section: text, image, video, or documentYes (text only)
BODYMain message contentYes ({{1}}, {{2}}, ... or {{name}}, {{order_number}}, ... -- see Parameter Formats)
FOOTERSmaller text below the bodyNo
BUTTONSURL buttons or quick repliesYes (URL suffix)
CAROUSELA swipeable set of up to 10 cards, shown beneath a top-level BODYPer 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}}]

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 its format is IMAGE or VIDEO (never TEXT or DOCUMENT).
  • All card headers use the same format (all IMAGE, or all VIDEO).
  • A card BODY is optional (a header-plus-buttons card is valid), but body presence must be consistent: either every card has a BODY or 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:

FormatPlaceholder syntaxExample
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",
"...": "..."
}
note

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:

CategoryUse CaseExamples
UTILITYTransaction-relatedOrder confirmations, shipping updates, appointment reminders
MARKETINGPromotionalOffers, product announcements, newsletters
AUTHENTICATIONLogin/verificationOTP codes, two-factor authentication
info

UTILITY templates are typically reviewed faster than MARKETING templates. AUTHENTICATION templates have specific format requirements set by Meta.

Template Statuses​

StatusDescription
APPROVEDReady to use for sending messages
PENDINGSubmitted and awaiting Meta review
REJECTEDDeclined by Meta (see rejection reason)
PAUSEDPaused 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
DISABLEDDisabled 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:

CodeLanguage
en_USEnglish (US)
en_GBEnglish (UK)
de_DEGerman
fr_FRFrench
es_ESSpanish
pt_BRPortuguese (Brazil)
it_ITItalian
nl_NLDutch
arArabic
warning

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), so GET /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}
]
}
]
}
FieldDescription
waba_idWhatsApp Business Account ID. null when the account of a channel can't be determined; that channel then forms its own group.
representative_channel_idThe 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.
channelsThe 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​

ParameterTypeDefaultDescription
channel_idstring--Filter by WhatsApp channel (UUID). Also triggers an auto-sync from Meta when that channel has pending templates
statusstring--Filter: APPROVED, PENDING, REJECTED, DISABLED
limitinteger100Max results to return (1--500)
offsetinteger0Number of results to skip
Pagination differs here

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.

note

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​

FieldTypeRequiredDescription
channel_idstringYesThe WhatsApp channel (and therefore WABA) to create the template on
namestringYesLowercase letters, digits and underscores only (^[a-z0-9_]+$), max 512 chars
categorystringYesUTILITY, MARKETING, or AUTHENTICATION
languagestringNoISO language code, default en
componentsarrayYesThe template layout — see below
parameter_formatstringNopositional (default) or named. Can be switched later with an edit
body_named_examplesobjectNoRequired when parameter_format is named: example value per placeholder name
allow_category_changebooleanNoDefault true — lets Meta reclassify the template rather than rejecting it
default_header_media_urlstringNoFallback 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_filenamestringNoFilename 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.

One upload-media call per card

Each 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:

StatuscodeMeaning
400TEMPLATE_CREATE_FAILEDThe request failed before it reached WhatsApp, for example the channel or its WhatsApp Business Account could not be found
422WHATSAPP_TEMPLATE_REJECTEDWhatsApp refused the template. message says what to change
429WHATSAPP_TEMPLATE_REJECTEDWhatsApp is rate-limiting your WhatsApp Business Account. Retry after the seconds in the Retry-After header (60)
503WHATSAPP_TEMPLATE_REJECTEDWhatsApp 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" }
}

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:

StatusEditableLimits
APPROVEDYes1 edit per 24 hours, 10 edits in a rolling 30 days, category locked
REJECTED, PAUSEDYesUnlimited; the category can change
PENDING, DISABLEDNo

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:

Statuserror_code
400template_channel_unavailable, or an invalid carousel structure
404template_not_found
409template_not_editable_status (detail.status holds the current status)
422template_identity_immutable, template_category_locked, template_edit_unsupported, template_invalid, header_media_required, template_edit_rejected
429template_edit_limit_reached (detail.next_edit_available_at, Retry-After when known), meta_rate_limited (Retry-After)
503meta_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.

Carousels round-trip

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.

Automatic sync and rate limits

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​