Skip to main content

Create WABA Template

Agent Skill

Automate this endpoint with an AI agent using the 2chat-whatsapp-waba skill:

npx skills add 2ChatCo/agent-skills -s 2chat-whatsapp-waba

Create a WhatsApp Business API (WABA) message template and submit it to WhatsApp for review.

info

Requires a WhatsApp Business API (WABA) channel connected to your 2Chat account. See How to connect WABA to 2Chat.

Approval is not instant

A new template is created with status PENDING and reviewed by WhatsApp — usually within minutes, occasionally up to 24 hours. It cannot be sent until its status is APPROVED. Follow it with Get WABA Template or subscribe to the whatsapp.waba.template.status.updated webhook.

Some templates — authentication ones in particular — are approved during this call, in which case the response already says APPROVED. Read template.status instead of assuming.

Endpoint

POST https://api.p.2chat.io/open/waba/templates

Authentication

Send your API key in the X-User-API-Key header. See Authentication.

Request body

ParameterDescription
phone_numberYour WABA number in E.164 format: no spaces, with leading plus sign. Required.
nameTemplate name. Lowercase letters, digits and underscores only, up to 512 characters. Required.
languageWhatsApp language code — any of the 111 languages WhatsApp supports, e.g. en, en_US, es_MX, pt_BR. Required.
categoryMARKETING, UTILITY or AUTHENTICATION. Required. WhatsApp may recategorize the template — read template.category in the response.
bodyMessage body, up to 1024 characters. Variables are written {{1}}, {{2}}, … — or, on Meta-hosted numbers, named: {{customer_name}} (letters, digits and underscores; one format per template, never mixed). With named variables, pass examples.body as an object keyed by name, e.g. {"customer_name": "Ana"}. Gupshup channels support numbered variables only. Required, except for AUTHENTICATION templates, where WhatsApp writes the body for you.
headerOptional object. Text: {"format": "TEXT", "text": "..."} — max 60 characters, at most one variable. Media: {"format": "IMAGE" \| "VIDEO" \| "DOCUMENT", "media_url": "https://..."} — see Media headers.
footerOptional footer text, up to 60 characters. No variables.
buttonsOptional array of button objects. See Buttons.
examplesSample values per component: {"body": [...], "header": [...], "button": [...]}. Required for every component that contains variables — one value per variable, in order.
add_security_recommendationAUTHENTICATION only. When true, WhatsApp appends its "do not share this code" line.
code_expiration_minutesAUTHENTICATION only. Between 1 and 90. Adds an expiry line to the footer.
descriptionOptional note stored with the template in 2Chat. Only kept for Gupshup-backed channels.

The examples keys match the params keys of Send WABA Message, so the values you supply here have the same shape as the values you will send later.

Supported languages

Templates can be written in any of the 111 languages WhatsApp supports. Pass the code in language exactly as listed below: lowercase language, uppercase region, joined by an underscore (en_US, not en-US or en_us). A plain language code and a regional one are different templates — es is not es_MX.

WhatsApp keys a template by name + language, so the same name can be reused once per language. That is how you offer one template in several languages: create it once per language, each reviewed on its own. Note that Delete WABA Template removes every language variant at once.

All 111 language codes
LanguageCode
Afrikaansaf
Albaniansq
Arabicar
Arabic (EGY)ar_EG
Arabic (LBN)ar_LB
Arabic (MAR)ar_MA
Arabic (QAT)ar_QA
Arabic (UAE)ar_AE
Azerbaijaniaz
Belarusianbe_BY
Bengalibn
Bengali (IND)bn_IN
Bulgarianbg
Catalanca
Chinese (CHN)zh_CN
Chinese (HKG)zh_HK
Chinese (TAI)zh_TW
Croatianhr
Czechcs
Danishda
Dariprs_AF
Dutchnl
Dutch (BEL)nl_BE
Englishen
English (AUS)en_AU
English (CAN)en_CA
English (GHA)en_GH
English (IND)en_IN
English (IRL)en_IE
English (JAM)en_JM
English (MYS)en_MY
English (NZL)en_NZ
English (QAT)en_QA
English (SGP)en_SG
English (UAE)en_AE
English (UGA)en_UG
English (UK)en_GB
English (US)en_US
English (ZAF)en_ZA
Estonianet
Filipinofil
Finnishfi
Frenchfr
French (BEL)fr_BE
French (CAN)fr_CA
French (CHE)fr_CH
French (CIV)fr_CI
French (MAR)fr_MA
Georgianka
Germande
German (AUT)de_AT
German (CHE)de_CH
Greekel
Gujaratigu
Hausaha
Hebrewhe
Hindihi
Hungarianhu
Indonesianid
Irishga
Italianit
Japaneseja
Kannadakn
Kazakhkk
Kinyarwandarw_RW
Koreanko
Kyrgyz (Kyrgyzstan)ky_KG
Laolo
Latvianlv
Lithuanianlt
Macedonianmk
Malayms
Malayalamml
Marathimr
Norwegiannb
Pashtops_AF
Persianfa
Polishpl
Portuguese (BR)pt_BR
Portuguese (POR)pt_PT
Punjabipa
Romanianro
Russianru
Serbiansr
Sinhalasi_LK
Slovaksk
Sloveniansl
Spanishes
Spanish (ARG)es_AR
Spanish (CHL)es_CL
Spanish (COL)es_CO
Spanish (CRI)es_CR
Spanish (DOM)es_DO
Spanish (ECU)es_EC
Spanish (HND)es_HN
Spanish (MEX)es_MX
Spanish (PAN)es_PA
Spanish (PER)es_PE
Spanish (SPA)es_ES
Spanish (URY)es_UY
Swahilisw
Swedishsv
Tamilta
Telugute
Thaith
Turkishtr
Ukrainianuk
Urduur
Uzbekuz
Vietnamesevi
Zuluzu

WhatsApp owns this list, and 2Chat does not gate it — when WhatsApp adds a language, you can pass the new code right away. A code that is well formed but unknown to WhatsApp comes back as a 400 prefixed with Meta:.

Media headers

Templates can carry an image, video or document header. WhatsApp requires a sample file to review the template — send it as a public URL in header.media_url and 2Chat uploads it to WhatsApp for you:

{
"phone_number": "+5215512345432",
"name": "order_invoice",
"language": "en",
"category": "UTILITY",
"header": { "format": "DOCUMENT", "media_url": "https://cdn.example.com/sample-invoice.pdf" },
"body": "Hi {{1}}, your invoice is attached.",
"examples": { "body": ["Ada"] }
}
FormatAccepted filesMax size
IMAGEJPEG, PNG5 MB
VIDEOMP416 MB
DOCUMENTPDF100 MB

The sample is only used for WhatsApp's review — the actual file goes out per message via header_media_url in Send WABA Message. header.text and header.media_url are mutually exclusive, and the URL must be publicly reachable (2Chat downloads it server-side; private hosts are rejected).

Buttons

Each button is an object with a type:

TypeFieldsLimit
QUICK_REPLYtextUp to 10
URLtext, url, and example when the URL contains {{1}}Up to 2
PHONE_NUMBERtext, phone_numberUp to 1
OTPotp_type (COPY_CODE or ONE_TAP), textExactly 1, AUTHENTICATION only

WhatsApp does not allow QUICK_REPLY buttons to be mixed with URL or PHONE_NUMBER buttons, and allows at most 2 call-to-action buttons in total. ONE_TAP buttons also require package_name and signature_hash.

An AUTHENTICATION template with no buttons gets a COPY_CODE button added automatically.

Invocation

curl --request POST \
--url 'https://api.p.2chat.io/open/waba/templates' \
--header 'Content-Type: application/json' \
--header 'X-User-API-Key: your_api_key_here' \
--data '{
"phone_number": "+5215512345432",
"name": "order_shipped",
"language": "en",
"category": "UTILITY",
"header": { "format": "TEXT", "text": "Order {{1}}" },
"body": "Hi {{1}}, your order {{2}} is on its way and arrives on {{3}}.",
"footer": "2Chat",
"examples": {
"header": ["A-8871"],
"body": ["Ada", "A-8871", "August 12"]
}
}'

Create a marketing template (text only)

The simplest marketing template: a text header, a body and a footer — no media, no buttons. Remember marketing messages need an unsubscribe path, and WhatsApp may still recategorize the template, so read template.category in the response.

{
"phone_number": "+5215512345432",
"name": "new_arrivals_2026",
"language": "en",
"category": "MARKETING",
"header": { "format": "TEXT", "text": "New arrivals" },
"body": "Hi {{1}}! Our new collection just landed — come take a look before it sells out.",
"footer": "Reply STOP to unsubscribe",
"examples": {
"body": ["Ada"]
}
}

Only the body contains a variable here, so examples carries just body. A header with no {{1}} needs no example.

Create a template with buttons

{
"phone_number": "+5215512345432",
"name": "summer_promo_2026",
"language": "en",
"category": "MARKETING",
"body": "{{1}}, enjoy 30% off until {{2}}.",
"footer": "Reply STOP to unsubscribe",
"buttons": [
{ "type": "URL", "text": "See offers", "url": "https://shop.example.com/promo/{{1}}" },
{ "type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+5215512345432" }
],
"examples": {
"body": ["Ada", "08/31"],
"button": ["summer"]
}
}

examples.button supplies the sample value for the dynamic URL button, in the same order the dynamic buttons appear.

Create an authentication template

WhatsApp writes the body of authentication templates itself (<VERIFICATION_CODE> is your verification code.), so body, header and footer do not apply — the only things you control are the security line, the expiry and the OTP button.

{
"phone_number": "+5215512345432",
"name": "login_code",
"language": "en",
"category": "AUTHENTICATION",
"add_security_recommendation": false,
"code_expiration_minutes": 30,
"buttons": [
{ "type": "OTP", "otp_type": "COPY_CODE", "text": "Copy Code" }
]
}

The verification code is sent as the first body parameter when you send the template. The example_payload in the response shows exactly where it goes.

Response

Returns 201 Created.

{
"success": true,
"template": {
"uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"name": "order_shipped",
"status": "PENDING",
"category": "UTILITY",
"language": "en",
"content": "Hi {{1}}, your order {{2}} is on its way and arrives on {{3}}.",
"description": null,
"components": [
{ "type": "HEADER", "format": "TEXT", "text": "Order {{1}}" },
{ "type": "BODY", "text": "Hi {{1}}, your order {{2}} is on its way and arrives on {{3}}." },
{ "type": "FOOTER", "text": "2Chat" }
],
"quality_score": "UNKNOWN",
"rejection_reason": null,
"updated_at": "2026-08-12T19:44:11Z"
},
"required_params": {
"send_supported": true,
"unsupported_reasons": [],
"body": [
{ "index": 1, "placeholder": "{{1}}", "required": true, "type": "string", "max_length": 1024, "example": "Ada" },
{ "index": 2, "placeholder": "{{2}}", "required": true, "type": "string", "max_length": 1024, "example": "A-8871" },
{ "index": 3, "placeholder": "{{3}}", "required": true, "type": "string", "max_length": 1024, "example": "August 12" }
],
"header": [
{ "index": 1, "placeholder": "{{1}}", "required": true, "type": "string", "max_length": 60, "example": "A-8871" }
],
"button": [],
"counts": { "body": 3, "header": 1, "button": 0 }
},
"example_payload": {
"from_number": "+5215512345432",
"to_number": "<to_number>",
"template_uuid": "TMP1b44a079-c75c-4403-bc8f-a75c4ce5cd23",
"params": {
"header": ["A-8871"],
"body": ["Ada", "A-8871", "August 12"]
}
},
"message": "Template submitted to WhatsApp for review. Approval is not instant (usually minutes, up to 24 hours) and the template cannot be sent until its status is APPROVED — poll GET /open/waba/templates/{template_uuid} or subscribe to the whatsapp.waba.template.status.updated webhook."
}
FieldDescription
successtrue when the template was created
templateThe created template, in the same shape as Get WABA Template
template.uuidTemplate UUID. Use it as template_uuid when sending, and to poll the approval status
template.statusPENDING, APPROVED or REJECTED. Only APPROVED templates can be sent
template.categoryThe category WhatsApp assigned. It may differ from the one you requested
template.rejection_reasonWhy WhatsApp rejected the template, once it is REJECTED
required_paramsThe values you must supply when sending, per component
example_payloadA ready-to-use body for Send WABA Message — replace <to_number> and the sample values
messageWhat to do next, based on the returned status

Error responses

{
"error": true,
"error_message": "Payload is invalid: `examples.body` is required when `body` contains placeholders"
}
StatusWhen
400The payload breaks a template rule: invalid name charset, unknown category, a malformed language code, a component over its length limit, variables not numbered consecutively from {{1}}, a body that starts or ends with a variable, missing or mismatched examples, an unsupported button combination, or a header.media_url that is missing, unreachable, of the wrong file type or over its size limit
400WhatsApp rejected the template. The message is prefixed with Meta: and carries their explanation — most often a duplicate name
401Missing or invalid API key
402The account has expired
404phone_number is not a WABA number on your account
429Too many templates created recently, or the same name and language is already being created
Creation limits

WhatsApp allows 100 template creations per WABA per hour, and caps how many templates an account can hold in total: 250 until the business portfolio is verified, 6,000 once verified. 2Chat caps API creation below the hourly allowance so an automated integration cannot exhaust it for your whole account, including templates created from the app: 40 creations per hour per WABA number and 100 per hour per 2Chat account. Requests over either cap return 429 — wait a few minutes and retry. If WhatsApp rejects a creation because the account is at its total capacity, delete unused templates first — keeping in mind deleted names stay reserved for 30 days.

After creating a template

  1. Poll Get WABA Template with the returned uuid, or subscribe to the whatsapp.waba.template.status.updated webhook to be told when WhatsApp decides.
  2. Once status is APPROVED, send it with Send WABA Message using the example_payload as a starting point.
  3. If it is REJECTED, read rejection_reason, fix the content and create a new template — a rejected template cannot be edited.

Every successful creation also fires the whatsapp.waba.template.created webhook, so other systems can react to templates created by someone else — including from the 2Chat web app.