Template Approval Process
Understand how WhatsApp templates are reviewed and approved by Meta
All WhatsApp templates must be approved by Meta before they can be used to send messages. This guide explains the approval process, common rejection reasons, and how to track template status.
Approval Workflow
stateDiagram-v2
[*] --> draft: Create template
draft --> pending: Submit for approval
pending --> approved: Meta approves
pending --> rejected: Meta rejects
rejected --> draft: Edit and fix
approved --> [*]: Ready to use
Submitting for Approval
Via Dashboard
- Go to Senders > Select sender > Templates tab
- Find the template with "Draft" status
- Click the ... menu and select Submit for Approval
- The status will change to "Pending"
Via API
// Submit a draft template for approval
await zavu.templates.submit({
templateId: 'tmpl_abc123',
});
Sample Values for Variables
Meta reviews a template against a sample value for each body variable. Send them as variableExamples when you submit: key a named variable ({{customer_name}}) by its name, and a positional one ({{1}}, {{2}}) as var1, var2.
terminalcurl -X POST https://api.zavu.dev/v1/templates/tmpl_abc123/submit \ -H "Authorization: Bearer $ZAVUDEV_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "senderId": "sender_abc123", "category": "UTILITY", "variableExamples": { "var1": "Maria", "var2": "ORD-12345" } }'
Any variable you leave out gets a generic placeholder such as Sample var1, which Meta reviews less favourably. Send a realistic value for every variable. The SDKs do not include this field yet, so use the REST endpoint for it.
Template Status
| Status | Description | Can Send? |
|---|---|---|
draft | Not yet submitted to Meta | No |
pending | Submitted, awaiting Meta review | No |
approved | Approved by Meta | Yes |
rejected | Rejected by Meta | No |
Approval Timeline
- Typical review time: 24-48 hours
- Complex templates: Up to 72 hours
- Peak periods: May take longer during high volume
Tracking Status Changes
Webhooks
Configure webhooks to receive real-time notifications when template status changes:
JSON{ "id": "evt_1702000000000_abc123", "type": "template.status_changed", "timestamp": 1702000000000, "senderId": "sender_xyz", "projectId": "proj_abc", "data": { "templateId": "tmpl_123", "name": "order_confirmation", "previousStatus": "pending", "currentStatus": "approved", "rejectionReason": null } }
Enable the webhook event:
- Go to Senders > Select sender > Webhooks tab
- Add
template.status_changedto your webhook events
A template belongs to the WhatsApp Business Account, not to one sender, so when several senders share that account the event is delivered once, to a single sender: the first one, by creation date, that has an active webhook configured. Configure the webhook on the sender you use and it will arrive there. If two senders on the same account point at different endpoints, only the older of the two receives it — split them across accounts if both need to know.
Polling
Check template status via API:
TypeScriptconst template = await zavu.templates.retrieve({ templateId: 'tmpl_abc123' }); console.log('Status:', template.whatsappStatus); if (template.rejectionReason) { console.log('Rejection reason:', template.rejectionReason); }
Syncing from WhatsApp
Two cases the webhook does not cover: a template created outside Zavu (in Meta
Business Manager, or by another tool) never appears in Zavu, and a missed
template.status_changed webhook leaves a template stuck in pending.
POST /v1/templates/sync reconciles both. It imports Meta templates Zavu does
not have — or links them to an existing template with the same name — and
refreshes the status of the ones it already knows about.
terminalcurl -X POST https://api.zavu.dev/v1/templates/sync \ -H "Authorization: Bearer $ZAVUDEV_API_KEY" \ -H "Content-Type: application/json" \ -d '{}'
JSON{ "accountsSynced": 1, "imported": 2, "linked": 1, "updated": 3, "skipped": 12, "errors": [] }
Every WhatsApp sender in the project is synced by default; pass
{"senderId": "sender_xyz"} to sync just one. The call waits for Meta, so it
takes a few seconds per account — list your templates right after it returns.
Templates that Meta reports as rejected or disabled are not imported and are
counted in skipped.
Common Rejection Reasons
Content Policy Violations
Formatting Issues
| Issue | Solution |
|---|---|
| Invalid variable format | Use {{1}}, {{2}} format |
| Too many variables | Limit to 10 variables per component |
| Variable in wrong position | Variables can't be at start of template |
| Button URL without variable | Dynamic URLs must include variables |
Category Mismatch
Templates may be rejected if the category doesn't match the content:
- UTILITY templates shouldn't contain promotional content
- MARKETING templates need clear opt-out language
- AUTHENTICATION templates should only contain OTP/verification content
Rejection is not the only outcome. More often Meta recategorizes the
template instead — typically UTILITY to MARKETING — and approves it under
the new category. It can also recategorize a template months after approval.
Either way the template keeps working and every send on it is billed at the new
category's rate.
template.category reports the category Meta currently assigns, not the one
you submitted. A recategorization is delivered as a template.status_changed
webhook carrying data.category, and it fires even when the approval status
did not move — so previousStatus and currentStatus can be identical on an
event that changes what you pay. Branch on category, not only on
currentStatus.
To reconcile templates that were recategorized before you were listening, run
POST /v1/templates/sync; it refreshes the category alongside the status.
Language Issues
- Template body must match the declared language
- Special characters may cause issues in some languages
- Emojis should be used sparingly
Fixing Rejected Templates
When a template is rejected:
- Check the rejection reason in the dashboard or API response
- Edit the template to fix the issues
- Resubmit for approval
TypeScript// Create the corrected template const fixed = await zavu.templates.create({ name: 'order_confirmation_v2', language: 'en', body: 'Updated message content that addresses the rejection reason', whatsappCategory: 'UTILITY', }); // Submit it to the WhatsApp Business Account on this sender await zavu.templates.submit(fixed.id, { senderId: 'sender_abc123' }); // Once the replacement is approved, drop the rejected one await zavu.templates.delete('tmpl_abc123');
Best Practices for Approval
Vague templates are more likely to be rejected. Be specific about the purpose.
Ensure your template category accurately reflects the message type.
UTILITY templates should be purely transactional without marketing content.
Marketing templates should include clear unsubscribe instructions.
Template Examples
Good UTILITY Template
CodeHi {{1}}, your order #{{2}} has shipped! Tracking number: {{3}} Estimated delivery: {{4}} Track your package: https://example.com/track/{{2}}
Good MARKETING Template
CodeHi {{1}}! We have a special offer just for you: {{2}}% off your next purchase! Use code: {{3}} Valid until: {{4}} Reply STOP to unsubscribe.
Good AUTHENTICATION Template
CodeYour verification code is {{1}}. This code expires in 10 minutes. Do not share this code with anyone.
Rate Limits
| Action | Limit |
|---|---|
| Submit templates | 100/minute |
| Templates per WABA | 250 total |