What Bulk Management Does
Two endpoints help when you need authentication templates in many languages or want to preview the localized text before creating templates:
| Endpoint | Purpose |
|---|---|
POST /<WABA_ID>/upsert_message_templates | Create or update authentication templates across multiple languages in a single call |
GET /<WABA_ID>/message_template_previews | Generate preview body, footer, and button text in any combination of languages and configuration options — without creating a template |
Both are specific to authentication templates (the preset-text structure makes localized previews tractable). Other categories don't have an upsert endpoint or a preview API.
When to Use Each
- Use previews before deciding which language/security/expiration combinations to ship — see exactly what the user will see in each language.
- Use upsert when you've decided on a configuration and want to roll it out across N languages in one transactional API call rather than N create/update calls. Especially useful when adding or refreshing language coverage.
Upsert Message Templates
POST /<WABA_ID>/upsert_message_templates creates a template if one with that name+language does not exist, or updates the existing one if it does. The same call can target multiple languages — Meta returns a per-language status array.
If you mix create and update in one call (some languages already exist, others don't), Meta handles each independently and returns the per-language outcome.
Upsert Payload Shape
{
"name": "<NAME>",
"languages": ["en_US", "es_ES", "fr"],
"category": "AUTHENTICATION",
"components": [
{
"type": "BODY",
"add_security_recommendation": true
},
{
"type": "FOOTER",
"code_expiration_minutes": 10
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "OTP",
"otp_type": "<OTP_TYPE>",
"supported_apps": [
{
"package_name": "<PACKAGE_NAME>",
"signature_hash": "<SIGNATURE_HASH>"
}
]
}
]
}
]
}
languages is the multi-language replacement for the singular language field used in single-template create.
Property Differences vs Single Create
All standard authentication template create properties are supported, with three exceptions:
| Property | Single create | Upsert |
|---|---|---|
language | Required string | Not supported — use languages array instead |
text (button label) | Optional | Not supported — labels fall back to localized defaults |
autofill_text | Optional | Not supported — falls back to localized default |
If you need custom button labels, use single-create per language instead.
Example: Bulk Copy-Code in 3 Languages
curl 'https://graph.facebook.com/<GRAPH_VERSION>/<WABA_ID>/upsert_message_templates' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"name": "authentication_code_copy_code_button",
"languages": ["en_US", "es_ES", "fr"],
"category": "AUTHENTICATION",
"components": [
{ "type": "BODY", "add_security_recommendation": true },
{ "type": "FOOTER", "code_expiration_minutes": 10 },
{
"type": "BUTTONS",
"buttons": [
{ "type": "OTP", "otp_type": "COPY_CODE" }
]
}
]
}'
Response — one entry per language, each with its own template id and review status:
{
"data": [
{ "id": "954638012257287", "status": "APPROVED", "language": "en_US" },
{ "id": "969725527415202", "status": "APPROVED", "language": "es_ES" },
{ "id": "969725530748535", "status": "APPROVED", "language": "fr" }
]
}
Authentication templates often auto-approve (no manual review needed) when the structure matches the preset format and the security/expiration values are within range — that's why this example shows APPROVED immediately. For other categories or non-trivial templates, expect PENDING.
Example: Bulk One-Tap with Update
This call updates the existing en_US template (if any) and creates new es_ES and fr templates with one-tap autofill buttons:
curl 'https://graph.facebook.com/<GRAPH_VERSION>/<WABA_ID>/upsert_message_templates' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"name": "authentication_code_autofill_button",
"languages": ["en_US", "es_ES", "fr"],
"category": "AUTHENTICATION",
"components": [
{ "type": "BODY", "add_security_recommendation": true },
{ "type": "FOOTER", "code_expiration_minutes": 15 },
{
"type": "BUTTONS",
"buttons": [
{
"type": "OTP",
"otp_type": "ONE_TAP",
"supported_apps": [
{
"package_name": "com.example.luckyshrub",
"signature_hash": "K8a/AINcGX7"
}
]
}
]
}
]
}'
For one-tap and zero-tap, see OTP Handshake & Android SDK for the runtime side.
Message Template Previews
GET /<WABA_ID>/message_template_previews returns the actual body, footer, and button text in your requested languages — useful for previewing what your users will see before committing to a template configuration.
Query parameters:
| Parameter | Required? | Notes |
|---|---|---|
category | Yes | Must be AUTHENTICATION |
languages | Optional | Comma-separated list (e.g. en_US,es_ES). Omit to get all supported languages. |
add_security_recommendation | Optional | true to include "For your security…" string. Omit or false to exclude. |
code_expiration_minutes | Optional | Integer 1–90. If included, footer string with this number is returned. Omit to exclude footer. |
button_types | Required | Comma-separated. For authentication this must be OTP. |
Example: Preview en_US + es_ES with Security and Expiration
curl 'https://graph.facebook.com/<GRAPH_VERSION>/<WABA_ID>/message_template_previews?category=AUTHENTICATION&languages=en_US,es_ES&add_security_recommendation=true&code_expiration_minutes=10&button_types=OTP' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
Response:
{
"data": [
{
"body": "*{{1}}* is your verification code. For your security, do not share this code.",
"buttons": [
{ "autofill_text": "Autofill", "text": "Copy code" }
],
"footer": "This code expires in 10 minutes.",
"language": "en_US"
},
{
"body": "Tu código de verificación es *{{1}}*. Por tu seguridad, no lo compartas.",
"buttons": [
{ "autofill_text": "Autocompletar", "text": "Copiar código" }
],
"footer": "Este código caduca en 10 minutos.",
"language": "es_ES"
}
]
}
The preview is exactly what would render if you created the template with the same configuration. Use it for design reviews, customer support copy, or to validate that a language code is supported before adding it to your upsert_message_templates call.