← Blog

·

How to create, edit and delete WhatsApp message templates via the API

Updated on October 29, 2026

To create a WhatsApp message template through the API, send POST /<WABA_ID>/message_templates with name, language, category and a components array that includes a sample value for every variable. Meta replies with the template id and a PENDING status, and the review result arrives through the message_template_status_update webhook. To edit, call POST /<TEMPLATE_ID>; to delete, call DELETE /<WABA_ID>/message_templates?name=..., adding hsm_id to remove a single language.

This guide covers the payload for every operation, the edit and delete limits, and the mistakes that most often break template automation. For what templates are and when you need them, see our guide to WhatsApp API message templates; here we focus on the template lifecycle through the WhatsApp Business API.

Where templates live after September 23, 2026

Since September 23, 2026, Meta has been splitting the old WhatsApp Business Account (WABA) into a WhatsApp account (the phone number and profile) and a messaging account (templates, billing and webhooks). The template docs now say “Messaging account”: templates are messaging account assets, and each messaging account has its own set.

In practice, your code does not change. Messaging accounts inherited the WABA ID, and the documentation examples still use /<WABA_ID>/message_templates, so the ID you already use keeps working for create, list and delete. The consequence that matters is a different one: templates are not shared across messaging accounts. If two providers use the same number, each one has its own messaging account and needs the templates created there. The full picture is in WhatsApp account vs. messaging account.

Before you start

  • Account ID: the WABA ID (which is also the messaging account ID). If you are not sure where to find it, see where to find the Phone Number ID and WABA ID.
  • Access token: it needs the whatsapp_business_management permission. According to the docs, a token with only whatsapp_business_messaging gets error code 200 when deleting. For production, use a permanent system user token.
  • Graph API version: the examples use v26.0. Swap in the version you run.
  • Account limits: up to 100 templates created per hour per messaging account. The total cap is 250 templates if the business portfolio is unverified, and up to 6,000 if it is verified and has an approved display name on at least one WhatsApp account.

Want to try it while you read? Create a Vectalk account and send your first message through the official API. Create free account →

Create request fields

FieldRequiredWhat it is
nameYesLowercase letters, numbers and underscores; up to 512 characters
languageYesLanguage code such as en_US, pt_BR or es_MX
categoryYesMARKETING, UTILITY or AUTHENTICATION
componentsIn practice, yesHEADER, BODY, FOOTER and BUTTONS
parameter_formatNoPOSITIONAL ({{1}}) or NAMED ({{first_name}})
message_send_ttl_secondsNoTime-to-live for sent messages
library_template_nameNoCreates the template from Meta’s template library

Names are not unique: you can have order_shipped in en_US and in es_MX, and each name + language pair is a separate template with its own ID. The create response has three fields:

{
  "id": "1252715608684590",
  "status": "PENDING",
  "category": "UTILITY"
}

Store the id. It is the key to check, edit and delete that specific language.

Example 1: utility template with positional variables

Every variable needs a sample value, or the creation fails. Body samples go in example.body_text, an array inside an array, in variable order.

curl -X POST "https://graph.facebook.com/v26.0/<WABA_ID>/message_templates" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "components": [
      {
        "type": "HEADER",
        "format": "TEXT",
        "text": "Order {{1}} shipped",
        "example": { "header_text": ["#4821"] }
      },
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order is on its way and should arrive by {{2}}. Tracking code: {{3}}.",
        "example": { "body_text": [["Ana", "Nov 5", "BR123456789"]] }
      },
      {
        "type": "FOOTER",
        "text": "Reply to this message if you need help"
      }
    ]
  }'

A text header takes only one variable. Its sample goes in header_text (a flat array), while the body sample goes in body_text (an array of arrays). Mixing up the two is one of the most common errors.

Example 2: named variables

With parameter_format: "NAMED", variables get names instead of numbers, and the samples move to body_text_named_params (header_text_named_params for the header), each with param_name and example:

{
  "name": "invoice_available",
  "language": "en_US",
  "category": "UTILITY",
  "parameter_format": "NAMED",
  "components": [
    {
      "type": "BODY",
      "text": "Hi {{first_name}}. Your invoice {{invoice_number}} is due on {{due_date}}.",
      "example": {
        "body_text_named_params": [
          { "param_name": "first_name", "example": "Carlos" },
          { "param_name": "invoice_number", "example": "2026-10-0193" },
          { "param_name": "due_date", "example": "November 10, 2026" }
        ]
      }
    }
  ]
}

Named variables make the send payload easier to read and stop {{2}} and {{3}} from getting swapped when someone rewrites the copy. Meta’s examples use lowercase names with underscores; follow the same pattern. The format also applies at send time: a NAMED template is sent with a parameter_name on each parameter, as shown in how to send a template through the API.

An invoice reminder for your own customer, like the one above, is a utility message. Collecting debts on behalf of third parties is prohibited by the WhatsApp Business Messaging Policy, in any category.

Example 3: image, video or document header

For a media header, the sample is not a URL: it is a handle generated by the Graph API Resumable Upload API. It takes two calls, and the first one uses your Meta App ID, not the WABA ID.

Step 1, start an upload session:

curl -X POST "https://graph.facebook.com/v26.0/<APP_ID>/uploads?file_name=banner.jpg&file_length=184320&file_type=image/jpeg" \
  -H "Authorization: Bearer <TOKEN>"

The response is {"id": "upload:<UPLOAD_SESSION_ID>"}. Step 2, upload the binary file to that session:

curl -X POST "https://graph.facebook.com/v26.0/upload:<UPLOAD_SESSION_ID>" \
  -H "Authorization: OAuth <TOKEN>" \
  -H "file_offset: 0" \
  --data-binary @banner.jpg

Note the Authorization: OAuth header, exactly as in the docs. The response is {"h": "4::aW..."}, and that value goes in example.header_handle:

{
  "type": "HEADER",
  "format": "IMAGE",
  "example": { "header_handle": ["4::aW..."] }
}

The Resumable Upload API accepts application/pdf, image/jpeg, image/jpg, image/png and video/mp4. Use IMAGE, VIDEO or DOCUMENT for format (GIF exists only for the Marketing Messages API). The sample file is only for review: at send time, you pass the real media for each recipient.

Example 4: marketing template with buttons

Buttons live in a single BUTTONS component. This campaign template combines a URL with a dynamic suffix, a coupon code and a quick reply:

{
  "name": "black_friday_coupon",
  "language": "en_US",
  "category": "MARKETING",
  "components": [
    {
      "type": "BODY",
      "text": "{{1}}, Black Friday is here: {{2}} off sitewide until Sunday.",
      "example": { "body_text": [["Marina", "20%"]] }
    },
    {
      "type": "BUTTONS",
      "buttons": [
        {
          "type": "URL",
          "text": "Shop deals",
          "url": "https://shop.example.com/deals?c={{1}}",
          "example": ["bf2026"]
        },
        { "type": "COPY_CODE", "example": "BF20" },
        { "type": "QUICK_REPLY", "text": "Stop promotions" }
      ]
    }
  ]
}

The URL takes one variable, always at the end. The URL sample is an array; the COPY_CODE sample is a string. When quick replies are mixed with other button types, they must be grouped together (all before or all after the others), or the API returns an error. Cards with an image and button per card use a different format, the carousel template.

Component limits

ElementLimit
Header text60 characters, 1 variable
Body1,024 characters
Footer60 characters
Button label (URL, phone, quick reply)25 characters
Coupon code sample20 characters
Button URL2,000 characters, 1 variable
Total buttons10
URL buttons2
Phone number buttons1
Copy code buttons1

The docs also warn that templates with 4 or more buttons, or with a quick reply combined with another button type, are not viewable on WhatsApp desktop. Don’t use Markdown in text headers.

Category: what happens if you get it wrong

The category you declare is not the final word. Since April 9, 2025, if you submit a template as UTILITY and Meta decides it is marketing, it is approved as MARKETING, with no need for the old allow_category_change: true (that behavior is now the default). In other cases, the template comes back REJECTED with reason INCORRECT_CATEGORY.

Meta also reviews templates that are already approved. Before changing the category of one, it fires a template_category_update webhook with a correct_category field; after the change, the event carries previous_category and new_category. For utility-to-marketing changes, the docs describe a 1-day advance notice, which is not given to businesses already warned for misuse. You have 60 days to appeal in Business Support Home.

The price gap makes this worth getting right. In Brazil, since October 1, 2026, a marketing message costs R$ 0.3217 (Brazilian reais) versus R$ 0.0350 for utility. Read how Meta categorizes templates before automating template creation.

Check template status

For one template, request the fields you need:

curl "https://graph.facebook.com/v26.0/<TEMPLATE_ID>?fields=name,language,status,category,rejected_reason,quality_score" \
  -H "Authorization: Bearer <TOKEN>"

To list templates, use GET /<WABA_ID>/message_templates. Without fields, the response includes name, parameter_format, components, language, status, category, sub_category and id. You can filter and paginate:

curl "https://graph.facebook.com/v26.0/<WABA_ID>/message_templates?fields=name,status,category&status=rejected&limit=50" \
  -H "Authorization: Bearer <TOKEN>"

The response has data and paging, with before and after cursors and a next URL. Follow next until it is gone.

Get status changes by webhook

Polling works, but the right way is to subscribe your app’s webhook to the message_template_status_update field. The event arrives with field: "message_template_status_update" and, inside value, the event, message_template_id, message_template_name, message_template_language, message_template_category and reason fields. An example with illustrative values:

{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "<WABA_ID>",
    "time": 1761730000,
    "changes": [{
      "field": "message_template_status_update",
      "value": {
        "event": "REJECTED",
        "message_template_id": 1252715608684590,
        "message_template_name": "black_friday_coupon",
        "message_template_language": "en_US",
        "message_template_category": "MARKETING",
        "reason": "INVALID_FORMAT",
        "rejection_info": {
          "reason": "...",
          "recommendation": "..."
        }
      }
    }]
  }]
}

The event values worth automating:

eventWhat to do
APPROVEDEnable the template in your system
REJECTEDRead reason (and rejection_info for INVALID_FORMAT), fix it and edit
PAUSED / FLAGGEDAlert the team; the template has poor quality
DISABLEDStop using it; disable_info carries the date
REINSTATED / UNARCHIVEDEnable it again
ARCHIVEDArchived for inactivity; deleted after 28 days unless unarchived
LIMIT_EXCEEDEDThe account has hit its template limit

reason values include INCORRECT_CATEGORY, TAG_CONTENT_MISMATCH, INVALID_FORMAT, PROMOTIONAL, ABUSIVE_CONTENT and SCAM. The usual fixes are in WhatsApp template rejected: common reasons and fixes, and pauses are covered in paused or disabled templates.

Edit a template

Editing is a POST to the template ID, and the components array you send replaces all components. You cannot edit one component on its own, so send the whole template.

curl -X POST "https://graph.facebook.com/v26.0/<TEMPLATE_ID>" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "components": [
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order is on its way and arrives by {{2}}. Tracking: {{3}}.",
        "example": { "body_text": [["Ana", "Nov 5", "BR123456789"]] }
      }
    ]
  }'

The response is {"success": true}. The rules from Meta’s template management page:

  • You can edit APPROVED, REJECTED or PAUSED templates (the API reference mentions approved and rejected).
  • Editable fields are category, components and time-to-live, but the category of an approved template cannot be edited.
  • An approved template can be edited at most once every 24 hours and up to 10 times in 30 days. Rejected and paused templates have no such limit.
  • After an edit, an approved or paused template goes through review again; if it passes, it stays approved.

To change the name, language or category of an approved template, create a new template.

Delete templates

Deletion uses the list endpoint with DELETE. There are three options:

# Deletes ALL languages with this name
curl -X DELETE "https://graph.facebook.com/v26.0/<WABA_ID>/message_templates?name=order_shipped" \
  -H "Authorization: Bearer <TOKEN>"

# Deletes only the template with this ID (one language)
curl -X DELETE "https://graph.facebook.com/v26.0/<WABA_ID>/message_templates?hsm_id=1252715608684590&name=order_shipped" \
  -H "Authorization: Bearer <TOKEN>"

The third is hsm_ids, a JSON array of up to 100 IDs. It cannot be combined with name or hsm_id, and a single invalid ID makes the whole request fail.

What happens next:

  • Messages already sent but not yet delivered keep the template as PENDING_DELETION, and WhatsApp keeps trying to deliver them for 30 days.
  • The name of a deleted approved template cannot be reused for 30 days.
  • Disabled templates cannot be deleted.
  • Deleting a name that does not exist returns an error.

Before running a cleanup script, remember that Meta archives templates unused for 12 months and schedules their deletion 28 days later. Sometimes you don’t need to clean up at all.

Common automation mistakes

  1. Missing sample: every variable needs one, including the button URL variable.
  2. header_text as an array of arrays, or body_text as a flat array: the shapes differ.
  3. A URL in header_handle: media samples need a Resumable Upload API handle.
  4. WABA ID in the upload call: the upload session is opened on the App ID.
  5. Token without whatsapp_business_management: it may list or create, but deletion fails with code 200.
  6. More than 100 creations per hour on one messaging account.
  7. Recreating a deleted approved template with the same name right away: wait 30 days or pick another name.
  8. Editing an approved template twice in one day: the second edit hits the 24-hour limit.

How to do it with Vectalk

Vectalk is a proxy for the official WhatsApp API, and the same operations are available on its API with an instance token (the WABA ID is resolved from the token, so it is not part of the URL):

OperationVectalk route
List or filter by nameGET https://apis.vectalk.com.br/api/templates?name=order_shipped
CreatePOST https://apis.vectalk.com.br/api/templates
Edit (only APPROVED or REJECTED)POST https://apis.vectalk.com.br/api/templates/{templateId}
Delete by nameDELETE https://apis.vectalk.com.br/api/templates?name=order_shipped

The create body uses Meta’s fields (name, language, category, components), and the response returns id, status: "PENDING" and category:

curl -X POST https://apis.vectalk.com.br/api/templates \
  -H "Authorization: Bearer <INSTANCE_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "components": [
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order is on its way and should arrive by {{2}}.",
        "example": { "body_text": [["Ana", "Nov 5"]] }
      }
    ]
  }'

Organization tokens cannot access templates, so create an instance-bound token under Settings → API Tokens. If you would rather not write JSON, the dashboard has a templates section with a Template Library. Every route is documented in the Vectalk templates reference, and you can sign up at apps.vectalk.com.br/register. For copy ideas by industry, see our 60 WhatsApp template examples.

Frequently asked questions

How long does Meta take to approve a template created via the API?

The template management page does not set a single deadline. The template starts as PENDING, and you learn the result from the message_template_status_update webhook or by calling GET /<TEMPLATE_ID>?fields=status. Don’t build a flow that depends on instant approval.

Should I use the WABA ID or the messaging account ID?

They are the same number. In the September 23, 2026 split, messaging accounts inherited the WABA ID, and the template docs still use /<WABA_ID>/message_templates.

Can I change the category of an approved template?

Not by editing: the docs say the category of an approved template cannot be edited. Create a new template with the right category. Meta itself can recategorize an approved template, and it tells you through the template_category_update webhook.

How do I create the same template in several languages?

Send one POST per language with the same name and a different language. Each one gets its own id. To delete a single language, use name + hsm_id.

Does the customer see the sample image?

No. The sample handle is only for Meta’s review. At send time, you pass the actual media in the header component, by link or media ID.


Official sources checked on October 29, 2026: Templates overview (Meta), Template management (Meta), Message Templates API (Meta), Template components (Meta), Template categorization (Meta), message_template_status_update webhook (Meta), Resumable Upload API (Meta), WhatsApp Business account updates (Meta), Vectalk templates API.

Put your business on the official WhatsApp

Create your account, connect your number to the official API (you can keep the app with coexistence) and start serving customers and sending messages.