Skip to main content

Send a Template Message

Template messages are the only way to initiate a WhatsApp conversation or send messages outside the 24-hour customer service window. This guide shows you how to send templates with variable substitution.

No approved template yet? Adopt one from Meta's Template Library — unchanged library templates are created already approved, and the id you get back is sent exactly like any other template below.

Required Scopes

ScopePurpose
messages:createSend template messages

Send Template Message

POST /api/v1/whatsapp-templates/{template_id_or_name}/send

The path segment accepts either the template's DB ID (a canonical UUID, e.g. f47ac10b-58cc-4372-a567-0e02b2c3d479) or the template's WhatsApp name (e.g. order_confirmation). A 36-character canonical UUID is treated as the ID; anything else is treated as a name.

Request Body

FieldTypeRequiredDescription
contact_idstringOne recipient field required¹The contact to send to. Uses the contact's first WhatsApp contact method, falling back to its phone number.
contact_method_idstringOne recipient field required¹A contact_methods.id of type whatsapp_id. Pins both the recipient and the owning contact. If contact_id is also sent it must name that same contact, otherwise the request returns 400.
whatsapp_idstringOne recipient field required¹Send straight to a phone number (no +) or an alphanumeric WhatsApp id, without looking up a contact first. A contact is matched or created automatically.
conversation_idstringOne recipient field required¹Send within an existing conversation (must be a WhatsApp conversation). Also disambiguates a template name shared across channels/languages.
channel_idstringNoThe WhatsApp channel to send from. Optional — see Choosing the channel.
languagestringNoThe template language to send when the name exists in more than one language. A Meta language/locale code (e.g. en, de, en_US, pt_BR). Optional — see Choosing the language.
variable_valuesstring[] | objectNoValues for the body placeholders. Shape depends on the template's parameter_format (see below).
header_media_urlstringNoHeader media for IMAGE/VIDEO/DOCUMENT templates. Accepts a bare attachment ID (UUID), a SendSeven proxy URL, a permanent public URL, a legacy GCS object ID, or an external https URL.
header_document_filenamestringNoDOCUMENT headers only. Overrides the filename WhatsApp shows for the document. Must include the correct extension matching the file (e.g. invoice.pdf for a PDF, report.docx for a Word file). Ignored for IMAGE/VIDEO/TEXT headers. See Document headers and filenames.

¹ At least one of contact_id, contact_method_id, whatsapp_id, or conversation_id must be present. channel_id and language are not recipients — they only select the sending channel and language variant — so they cannot satisfy this requirement on their own. Omitting all four recipient fields returns 422.

Identifying the recipient

You can address the recipient four different ways. Send any one of these fields (you may combine some — see the priority rules). This makes the endpoint flexible for both contact-centric apps and stateless integrations that just have a phone number.

FieldUse when…Contact handling
contact_idYou already manage contacts in SendSeven.Uses the contact's first whatsapp_id method, falling back to its phone number.
contact_method_idA contact has multiple WhatsApp numbers and you must target a specific one.Pins the exact method and its owning contact.
whatsapp_idYou just have a phone number / WhatsApp id (typical for integrations).An existing contact with that identifier is reused; otherwise a new contact and method are created.
conversation_idYou're continuing a specific existing thread.Derived from the conversation.

Resolution priority (first match wins)

When more than one recipient field is supplied, they are resolved in this fixed order:

  1. contact_method_id — the method's value is the recipient and its owning contact is used. The method must be of type whatsapp_id (any other type → 400). If contact_id is also sent it must name the method's owning contact — a mismatch returns 400 (it is no longer silently ignored).
  2. conversation_id — the conversation must belong to a WhatsApp channel (400 otherwise). The recipient and contact are taken from the conversation.
  3. whatsapp_id — the value is normalized (a leading + is stripped; spaces, dashes and parentheses are tolerated on phone-shaped values; alphanumeric ids are kept as-is):
    • If contact_id is also sent, that contact is used and gains a whatsapp_id contact method with this value (created if it doesn't already exist).
    • Otherwise, an existing contact is matched by this identifier; if none exists, a new contact + whatsapp_id method are created.
  4. contact_id — uses the contact's first whatsapp_id method, falling back to its phone number.

Choosing the channel

The message is always sent from the template's own channel — every template belongs to one channel. channel_id is optional and is used to:

  • Disambiguate a template name that exists on more than one channel. When you reference a template by name:
    • if channel_id is given, that channel's copy is used;
    • else, if conversation_id is given, the conversation's channel is used;
    • else, the first matching channel is used. Channel ambiguity never raises an error. (Language ambiguity within the chosen channel is handled separately — see below.)
  • Select the sending channel when sending by whatsapp_id with no contact or conversation.

If you pass a channel_id that conflicts with the resolved template's own channel (e.g. you reference a template by ID that lives on a different channel, or by a name that doesn't exist on the given channel), the request returns 400.

Choosing the language

The same template name can exist as several rows — one per language. After the channel is settled, the language variant is chosen as follows:

  • By DB ID — the language is fixed by that row. If you also pass a language that differs from the row's language, the request returns 400. (Reference the template by name to choose a language.)
  • By name, only one language exists — that language is used; language is optional.
  • By name, multiple languages exist:
    1. if you pass language, that exact variant is used — 400 if the name has no variant in that language (the error lists the available languages);
    2. otherwise SendSeven auto-resolves from the recipient contact's preferred language (Contact.language), matching the code exactly first, then by base-language family (so de matches de_DE, and pt_BR matches pt);
    3. if there is no language and the contact has no language (or none that matches an available variant), the request returns 400 asking you to specify language or set the contact's language.
note

Because language auto-resolution reads the recipient contact, sending by a bare whatsapp_id that creates a brand-new contact (which has no language yet) against a multi-language template will return the 400 in step 3 unless you pass language explicitly.

language must be the exact Meta language/locale code stored on the template (e.g. en, de, en_US, pt_BR). All language variants of a template share the same parameter shape, so variable_values is identical regardless of which language is selected.

Document headers and filenames

For templates with a DOCUMENT header, WhatsApp displays a filename on the document bubble. You can control it with header_document_filename. When you do not pass one, SendSeven resolves the filename using this precedence:

  1. header_document_filename in the request (per-send override), else
  2. the template's default_header_document_filename (the default you can set per template under Settings → WhatsApp Templates, or via the template create/update API), else
  3. the original filename of the uploaded attachment, when header_media_url resolves to a file uploaded through SendSeven (attachment ID, proxy/public URL, or GCS ID), else
  4. nothing — the field is omitted and WhatsApp decides what to display. This is the case for external https URLs with no stored filename.
caution

The filename must include the correct file extension for the document's MIME type (e.g. .pdf, .docx, .xlsx). A mismatched or missing extension can cause WhatsApp to display the document incorrectly or to reject it.

curl (document template with filename override)

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_invoice_789/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"variable_values": ["John", "ORD-5678"],
"header_media_url": "9f3c8b1a-2d44-4e6f-8a91-0b7c6d5e4f3a",
"header_document_filename": "invoice-ORD-5678.pdf"
}'

Recipient examples

These examples all use the same positional template tpl_abc123; swap in your own template ID/name and recipient field. See Variable Structure below for variable_values shapes.

Send to a contact (contact_id)

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"variable_values": ["John", "ORD-5678"]
}'

Send to a specific WhatsApp number on a contact (contact_method_id)

Use this when a contact has more than one WhatsApp number and you must target one exactly. The method must be of type whatsapp_id.

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_method_id": "cm_9f3c8b1a2d44",
"variable_values": ["John", "ORD-5678"]
}'

Send straight to a phone number or WhatsApp id (whatsapp_id)

No contact lookup required — ideal for integrations. The number is given without a + (a leading + is stripped automatically). A contact is matched by this identifier, or created if none exists.

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"whatsapp_id": "447700900123",
"variable_values": ["John", "ORD-5678"]
}'

To attach that number to an existing contact (creating the whatsapp_id method if missing), send both contact_id and whatsapp_id:

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"whatsapp_id": "447700900123",
"variable_values": ["John", "ORD-5678"]
}'

Send within an existing conversation (conversation_id)

The conversation must belong to a WhatsApp channel.

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"conversation_id": "b5d8e2a1-4c3f-4a8b-9d2e-1f6a7b8c9d0e",
"variable_values": ["Sarah", "INV-2026-001"]
}'

Pin the sending channel (channel_id)

When you reference a template by name and that name exists on multiple WhatsApp channels, add channel_id to choose which channel sends it. channel_id is always optional and may be combined with any recipient field.

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/order_confirmation/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"whatsapp_id": "447700900123",
"channel_id": "channel_wa_001",
"variable_values": ["John", "ORD-5678"]
}'

Pick the template language (language)

When a template name exists in several languages, pass language to choose the variant. You can combine it with any recipient field and with channel_id.

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/order_confirmation/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"language": "de",
"variable_values": ["John", "ORD-5678"]
}'

If you omit language, the contact's preferred language (Contact.language) is used when it matches an available variant; otherwise the request returns 400 listing the available languages.

Variable Structure

Every WhatsApp template has a fixed parameter_format of either positional ({{1}}, {{2}}, ...) or named ({{name}}, {{order_number}}, ...). The shape you pass for variable_values must match.

Positional templates

For templates with parameter_format: "positional", pass variable_values as either a list (recommended) or a dict keyed by the placeholder number.

List form (recommended):

{
"variable_values": ["John", "ORD-5678", "Feb 15, 2026"]
}

The first item fills {{1}}, the second fills {{2}}, and so on.

Equivalent dict form:

{
"variable_values": {"1": "John", "2": "ORD-5678", "3": "Feb 15, 2026"}
}

Numeric string keys are sorted by their integer value before substitution, so the order of keys in the JSON does not matter.

Named templates

For templates with parameter_format: "named", pass variable_values as an object whose keys match the template's named placeholders.

{
"variable_values": {
"name": "John",
"quote": "ORD-5678",
"due_date": "Feb 15, 2026"
}
}
tip

The order of keys does not matter. Meta resolves named parameters by parameter_name, so you can list keys in any order as long as each key matches a placeholder in the template body.

Knowing which format to use

Fetch the template via GET /api/v1/whatsapp-templates/{template_id} and check the parameter_format field on the response. Branch your client code on that value:

{
"id": "tpl_abc123",
"name": "order_confirmation",
"parameter_format": "named",
"...": "..."
}

A template's parameter_format is fixed at creation time and never changes, so you can cache it alongside the template ID.

Error cases

If you send the wrong shape for a template's parameter_format, the API returns 400 VALIDATION_ERROR with one of these messages:

TriggerResponse message
Sent a list against a template with parameter_format: "named"Template uses named parameters; pass values as an object keyed by parameter name
Sent a name-keyed dict against a template with parameter_format: "positional"Template uses positional parameters; pass values as a list or as a dict keyed by '1', '2', ...

curl (positional template, list form)

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_abc123/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"variable_values": ["John", "ORD-5678", "Feb 15, 2026"]
}'

curl (named template)

curl -X POST "https://api.sendseven.com/api/v1/whatsapp-templates/tpl_named_456/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_xyz789",
"variable_values": {
"name": "John",
"quote": "ORD-5678",
"due_date": "Feb 15, 2026"
}
}'

Python

import requests

BASE_URL = "https://api.sendseven.com/api/v1"
HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
}

# Positional template -- pass a list
positional_response = requests.post(
f"{BASE_URL}/whatsapp-templates/tpl_abc123/send",
headers=HEADERS,
json={
"contact_id": "contact_xyz789",
"variable_values": ["John", "ORD-5678", "Feb 15, 2026"],
},
)

# Named template -- pass a dict keyed by parameter name (order is irrelevant)
named_response = requests.post(
f"{BASE_URL}/whatsapp-templates/tpl_named_456/send",
headers=HEADERS,
json={
"contact_id": "contact_xyz789",
"variable_values": {
"name": "John",
"quote": "ORD-5678",
"due_date": "Feb 15, 2026",
},
},
)
print(named_response.json()["message_id"])

JavaScript

const BASE_URL = "https://api.sendseven.com/api/v1";
const HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
};

// Positional template -- pass an array
await fetch(`${BASE_URL}/whatsapp-templates/tpl_abc123/send`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
contact_id: "contact_xyz789",
variable_values: ["John", "ORD-5678", "Feb 15, 2026"],
}),
});

// Named template -- pass an object keyed by parameter name
const response = await fetch(`${BASE_URL}/whatsapp-templates/tpl_named_456/send`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
contact_id: "contact_xyz789",
variable_values: {
name: "John",
quote: "ORD-5678",
due_date: "Feb 15, 2026",
},
}),
});
const result = await response.json();
console.log(`Template sent: ${result.message_id}`);

Response

A successful send returns the SendTemplateResponse shape:

{
"success": true,
"message_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"external_id": "wamid.HBgNNDkxNTIxNTU4MDg2NRUCABIYFjNFQjBCNUYxMjdFRkI5",
"conversation_id": "b5d8e2a1-4c3f-4a8b-9d2e-1f6a7b8c9d0e",
"error": null,
"error_code": null,
"error_details": null
}
FieldTypeDescription
successbooleantrue when the template was accepted by WhatsApp
message_idstring | nullInternal SendSeven message ID
external_idstring | nullWhatsApp message ID (wamid…)
conversation_idstringConversation the message was sent in (new or existing)
errorstring | nullHuman-readable error when success is false
error_codeinteger | nullWhatsApp API error code (e.g. 131008 missing parameters, 131042 payment issue)
error_detailsstring | nullAdditional detail from Meta (e.g. which parameter is missing)
note

On a successful call the response success field is always true. If WhatsApp rejects the send, the API returns HTTP 400 with a detail object carrying the failure, e.g. {"detail": {"message": "…", "error_code": 131008, "error_details": "…"}} — the error* fields on the success schema above are only populated in edge cases, so treat a non-2xx status as the failure signal.

Variable Mapping

SendSeven supports automatic variable mappings that resolve to real values at send time. The mapping keys below can appear as values inside variable_values (in either list or dict form) and are replaced server-side before the message is dispatched to Meta:

Mapping KeyResolves To
contact.nameContact's display name
contact.phoneContact's phone number
contact.emailContact's email address
agent.nameAssigned agent's full name
agent.nicknameAssigned agent's nickname
tenant.nameYour organization name

Example with Auto-Mapping (positional)

{
"contact_id": "contact_xyz789",
"channel_id": "channel_wa_001",
"language": "en_US",
"variable_values": ["contact.name", "ORD-5678", "Feb 15, 2026"]
}

channel_id and language are optional here — included only to show they may accompany variable_values. See Choosing the channel and Choosing the language.

Example with Auto-Mapping (named)

{
"contact_id": "contact_xyz789",
"channel_id": "channel_wa_001",
"language": "en_US",
"variable_values": {
"name": "contact.name",
"quote": "ORD-5678",
"due_date": "Feb 15, 2026"
}
}

In both examples contact.name is automatically resolved to the contact's actual name (e.g., "John Smith") before sending.

tip

Auto-mapping is useful for personalization at scale -- you do not need to look up contact details before sending templates.

Error Responses

StatusError CodeDescription
400VALIDATION_ERRORMissing variables, wrong variable_values shape for the template's parameter_format, invalid language, or template not approved
400VALIDATION_ERRORcontact_method_id does not reference a whatsapp_id method
400VALIDATION_ERRORcontact_method_id and contact_id name different contacts
400VALIDATION_ERRORconversation_id does not reference a WhatsApp conversation
400VALIDATION_ERRORchannel_id conflicts with the resolved template's own channel (or the named template does not exist on that channel)
400VALIDATION_ERRORlanguage does not match the template referenced by ID, or the named template has no variant in the requested language
400VALIDATION_ERRORTemplate name exists in multiple languages and no language (or matching contact language) was provided
401INVALID_TOKENMissing or invalid authentication token
403INSUFFICIENT_SCOPEToken lacks required scope
404RESOURCE_NOT_FOUNDTemplate, contact, contact method, or conversation not found
422VALIDATION_ERRORNo recipient supplied — provide one of contact_id, contact_method_id, whatsapp_id, or conversation_id
422VALIDATION_ERRORResolved recipient has no WhatsApp id or phone number for delivery

Example error:

{
"detail": "Template uses named parameters; pass values as an object keyed by parameter name",
"error_code": "VALIDATION_ERROR"
}

Next Steps