APIs & Integrações28 de fev. de 2026· 14 min leitura

WhatsApp Template Rejected or Paused: How to Understand and Fix It

Understand Meta's quality rating system (green/yellow/red), why templates go from APPROVED to PAUSED or DISABLED, how to monitor via API and Business Manager, appeal rejections, and build a Slack/Discord alert bot.

You created a template, it got approved, everything was working — and then it stops. Status changed to PAUSED without any clear warning. Or worse: you submitted a new template and got REJECTED with a vague reason that explains nothing.

This frustrating cycle is everyday life for anyone working with WhatsApp Business API in production. This post explains how Meta's quality system really works, what causes each status transition, how to monitor programmatically, and how to respond to each scenario.

Meta's Quality Rating System

Every Business phone number in the WhatsApp API has a Quality Rating — a quality score based on user behavior from the past 7 days.

The rating has three levels:

  • Green (High quality) — Block and report rates are within limits. Everything operating normally.
  • Yellow (Medium quality) — Elevated block rate. Warning that changes are needed before things get worse.
  • Red (Low quality) — Critical block rate. Templates automatically paused, sending capacity reduced.

What Moves the Rating

Meta doesn't disclose exact thresholds, but the signals that negatively impact it are well-known:

  1. Users blocking your number — The heaviest action. When someone blocks, it's a strong signal of unwanted messaging.
  2. Users reporting spamReport Spam within WhatsApp. Immediate penalty.
  3. High read rate without replies — Indicator of irrelevant content.
  4. Sudden spike in send volume — Blasting a large volume without prior history raises flags.

The rating is calculated per phone number, not per WABA account. If you have multiple numbers in the same account, each has its own rating and affects its own templates.

Template Statuses and How They Transition

A template can be in any of these statuses:

| Status | Description | |--------|-------------| | PENDING | Awaiting Meta's review | | APPROVED | Approved, ready to use | | REJECTED | Rejected during review. Cannot be sent. | | PAUSED | Automatically paused due to low quality | | DISABLED | Permanently disabled | | IN_APPEAL | Under appeal process |

APPROVED → PAUSED

This transition happens automatically when Meta detects that the block/report rate associated with the template is high. The criteria considers:

  • The volume of messages sent by the template
  • The proportion of users who blocked or reported after receiving it

The template may be temporarily paused (and automatically reactivated if quality recovers) or paused indefinitely until you take action.

PAUSED → DISABLED

If a template stays PAUSED for an extended period without quality improvement, or if there are multiple recurring pauses, Meta may move it to DISABLED. In this state:

  • The template cannot be sent
  • Cannot be edited
  • Can be deleted, but the name stays blocked for 30 days

APPROVED → REJECTED (Post-approval)

Yes, this exists. Meta can review already-approved templates and revoke approval if it detects usage inconsistent with the declared content — for example, a UTILITY template being used to send promotions.

Why Templates Get Rejected

Rejection happens during the review phase (PENDING status). Meta returns a rejection_reason indicating the category of the problem.

Main Rejection Reasons

INVALID_FORMAT Structural JSON problem: variables without sequential index, missing examples, invalid components for the category.

// ❌ Variable {{2}} without {{1}} first
{
  "type": "BODY",
  "text": "Your order {{2}} has been shipped."
}

// ✅ Correct
{
  "type": "BODY",
  "text": "Hi, {{1}}! Your order {{2}} has been shipped.",
  "example": {
    "body_text": [["John", "US-98765"]]
  }
}

SCAM Content Meta identifies as potential fraud: prize promises, suspicious links, requests for sensitive data.

ABUSIVE_CONTENT Offensive language, adult content, or anything that violates WhatsApp's terms of service.

PROMOTIONAL Template submitted as UTILITY or AUTHENTICATION with clearly promotional content. Meta reclassifies or rejects.

INCORRECT_CATEGORY The declared category doesn't match the content. Unlike PROMOTIONAL, this can go in any direction.

NONE Rejection without a specified reason. Frustrating, but it happens — usually in automated reviews that identified a pattern without being able to categorize it.

Querying the Rejection Reason via API

curl "https://graph.facebook.com/v21.0/YOUR_WABA_ID/message_templates?name=template_name&fields=name,status,rejected_reason" \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "data": [
    {
      "name": "special_offer",
      "status": "REJECTED",
      "rejected_reason": "PROMOTIONAL",
      "id": "123456789012345"
    }
  ]
}

Monitoring Quality via API

Phone Number Quality Rating

curl "https://graph.facebook.com/v21.0/YOUR_PHONE_NUMBER_ID?fields=quality_rating,messaging_limit_tier" \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "quality_rating": "GREEN",
  "messaging_limit_tier": "TIER_1K",
  "id": "YOUR_PHONE_NUMBER_ID"
}

Possible quality_rating values: GREEN, YELLOW, RED, UNKNOWN.

messaging_limit_tier values:

  • TIER_NOT_SET — Unverified: 250 conversations/24h
  • TIER_50 — 50 conversations/24h (new account, not yet scaled)
  • TIER_1K — 1,000 conversations/24h
  • TIER_10K — 10,000 conversations/24h
  • TIER_100K — 100,000 conversations/24h
  • TIER_UNLIMITED — No limit

Listing Templates with Quality Status

curl "https://graph.facebook.com/v21.0/YOUR_WABA_ID/message_templates?fields=name,status,quality_score,rejected_reason" \
  -H "Authorization: Bearer YOUR_TOKEN"
{
  "data": [
    {
      "name": "order_notification",
      "status": "APPROVED",
      "quality_score": {
        "score": "GREEN",
        "date": 1709164800,
        "reasons": []
      },
      "id": "111111111111111"
    },
    {
      "name": "weekly_offer",
      "status": "PAUSED",
      "quality_score": {
        "score": "RED",
        "date": 1709078400,
        "reasons": ["BLOCKED"]
      },
      "id": "222222222222222"
    }
  ]
}

The quality_score.reasons field can contain: BLOCKED, REPORTED, READ (all negative when present).

Node.js Health Audit Script

const axios = require('axios')

const WABA_ID = process.env.WHATSAPP_WABA_ID
const PHONE_ID = process.env.WHATSAPP_PHONE_NUMBER_ID
const TOKEN = process.env.WHATSAPP_TOKEN

async function auditTemplateHealth() {
  // Check phone number rating
  const phoneRes = await axios.get(
    `https://graph.facebook.com/v21.0/${PHONE_ID}`,
    {
      params: {
        fields: 'quality_rating,messaging_limit_tier,display_phone_number',
      },
      headers: { Authorization: `Bearer ${TOKEN}` },
    }
  )

  const phone = phoneRes.data
  console.log(`\n📱 Number: ${phone.display_phone_number}`)
  console.log(`⭐ Quality Rating: ${phone.quality_rating}`)
  console.log(`📊 Messaging Tier: ${phone.messaging_limit_tier}`)

  // Check templates
  const templatesRes = await axios.get(
    `https://graph.facebook.com/v21.0/${WABA_ID}/message_templates`,
    {
      params: {
        fields: 'name,status,quality_score,rejected_reason,language',
        limit: 100,
      },
      headers: { Authorization: `Bearer ${TOKEN}` },
    }
  )

  const templates = templatesRes.data.data
  const byStatus = templates.reduce((acc, t) => {
    acc[t.status] = acc[t.status] || []
    acc[t.status].push(t)
    return acc
  }, {})

  console.log(`\n📋 Templates by status:`)
  for (const [status, list] of Object.entries(byStatus)) {
    const emoji =
      status === 'APPROVED'
        ? '✅'
        : status === 'PAUSED'
        ? '⏸️'
        : status === 'REJECTED'
        ? '❌'
        : status === 'DISABLED'
        ? '🚫'
        : '⏳'
    console.log(`  ${emoji} ${status}: ${list.length}`)
  }

  // Alert on problems
  const problematic = templates.filter((t) =>
    ['PAUSED', 'REJECTED', 'DISABLED'].includes(t.status)
  )

  if (problematic.length > 0) {
    console.log(`\n⚠️ Problematic templates:`)
    for (const t of problematic) {
      console.log(`  - ${t.name} (${t.language}): ${t.status}`)
      if (t.rejected_reason) {
        console.log(`    Reason: ${t.rejected_reason}`)
      }
      if (t.quality_score?.reasons?.length > 0) {
        console.log(`    Quality reasons: ${t.quality_score.reasons.join(', ')}`)
      }
    }
  }

  return { phone, templates }
}

auditTemplateHealth().catch(console.error)

Monitoring via Business Manager

For those who prefer the visual interface, the path in Meta Business Manager is:

  1. Go to business.facebook.com
  2. Click WhatsApp Manager (in the side menu)
  3. Select your WABA account
  4. Click Message Templates in the top bar
  5. Filter by status using the dropdown menu at the top of the list

For the number quality rating:

  1. In WhatsApp Manager, go to Phone Numbers
  2. Click on the desired number
  3. The Quality Rating column shows the current status with a color indicator

Business Manager shows the quality change history from the past 30 days, which is useful for correlating with specific campaigns.

Webhook: Real-Time Monitoring

The most efficient way to react to status changes is via webhook. Meta sends a message_template_status_update event whenever a template changes status.

Configuring the Webhook

The templates webhook uses the same endpoint you already configured for messages. Make sure the message_template_status_update field is subscribed:

curl -X POST \
  "https://graph.facebook.com/v21.0/YOUR_APP_ID/subscriptions" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "object": "whatsapp_business_account",
    "callback_url": "https://your-server.com/webhooks/whatsapp",
    "verify_token": "your_verify_token",
    "fields": ["messages", "message_template_status_update"]
  }'

Event Payload

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "YOUR_WABA_ID",
      "changes": [
        {
          "value": {
            "event": "APPROVED",
            "message_template_id": 123456789012345,
            "message_template_name": "order_notification",
            "message_template_language": "en_US",
            "reason": null
          },
          "field": "message_template_status_update"
        }
      ]
    }
  ]
}

For rejected templates, the reason field is populated:

{
  "value": {
    "event": "REJECTED",
    "message_template_id": 222222222222222,
    "message_template_name": "weekly_offer",
    "message_template_language": "en_US",
    "reason": "PROMOTIONAL"
  },
  "field": "message_template_status_update"
}

For templates paused due to quality:

{
  "value": {
    "event": "FLAGGED",
    "message_template_id": 333333333333333,
    "message_template_name": "retention_campaign",
    "message_template_language": "en_US",
    "reason": "QUALITY"
  },
  "field": "message_template_status_update"
}

Possible values for the event field:

  • APPROVED — Template approved
  • REJECTED — Template rejected
  • FLAGGED — Template paused (quality issue)
  • DISABLED — Template disabled
  • REINSTATED — Template reactivated after quality improvement
  • DELETED — Template deleted

Slack/Discord Alert Bot

With the webhook configured, the natural next step is building a bot that notifies your team when a template changes status. Here's a complete implementation using Express:

const express = require('express')
const axios = require('axios')
const crypto = require('crypto')

const app = express()

// Configuration
const config = {
  verifyToken: process.env.WHATSAPP_VERIFY_TOKEN,
  appSecret: process.env.WHATSAPP_APP_SECRET,
  slackWebhookUrl: process.env.SLACK_WEBHOOK_URL,
  discordWebhookUrl: process.env.DISCORD_WEBHOOK_URL,
}

// Middleware to parse JSON and validate signature
app.use(
  '/webhooks/whatsapp',
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf
    },
  })
)

// Meta signature validation (mandatory in production)
function validateSignature(req) {
  const signature = req.headers['x-hub-signature-256']
  if (!signature) return false

  const expectedSignature = crypto
    .createHmac('sha256', config.appSecret)
    .update(req.rawBody)
    .digest('hex')

  return signature === `sha256=${expectedSignature}`
}

// Format message for Slack
function formatSlackMessage(event) {
  const statusEmoji = {
    APPROVED: '✅',
    REJECTED: '❌',
    FLAGGED: '⏸️',
    DISABLED: '🚫',
    REINSTATED: '🔄',
    DELETED: '🗑️',
  }

  const emoji = statusEmoji[event.event] || '⚠️'
  const color =
    event.event === 'APPROVED' || event.event === 'REINSTATED'
      ? 'good'
      : event.event === 'FLAGGED'
      ? 'warning'
      : 'danger'

  return {
    attachments: [
      {
        color,
        title: `${emoji} WhatsApp Template: ${event.event}`,
        fields: [
          {
            title: 'Template',
            value: `\`${event.message_template_name}\``,
            short: true,
          },
          {
            title: 'Language',
            value: event.message_template_language,
            short: true,
          },
          {
            title: 'ID',
            value: String(event.message_template_id),
            short: true,
          },
          ...(event.reason
            ? [{ title: 'Reason', value: event.reason, short: true }]
            : []),
        ],
        footer: 'WhatsApp Business API',
        ts: Math.floor(Date.now() / 1000),
      },
    ],
  }
}

// Format message for Discord
function formatDiscordMessage(event) {
  const statusEmoji = {
    APPROVED: '✅',
    REJECTED: '❌',
    FLAGGED: '⏸️',
    DISABLED: '🚫',
    REINSTATED: '🔄',
    DELETED: '🗑️',
  }

  const colorMap = {
    APPROVED: 0x2eb67d, // green
    REINSTATED: 0x2eb67d,
    FLAGGED: 0xecb22e, // yellow
    REJECTED: 0xe01e5a, // red
    DISABLED: 0xe01e5a,
    DELETED: 0x808080, // gray
  }

  const emoji = statusEmoji[event.event] || '⚠️'
  const color = colorMap[event.event] || 0x808080

  const fields = [
    {
      name: 'Template',
      value: `\`${event.message_template_name}\``,
      inline: true,
    },
    { name: 'Language', value: event.message_template_language, inline: true },
    { name: 'ID', value: String(event.message_template_id), inline: true },
  ]

  if (event.reason) {
    fields.push({ name: 'Reason', value: event.reason, inline: true })
  }

  return {
    embeds: [
      {
        title: `${emoji} WhatsApp Template: ${event.event}`,
        color,
        fields,
        footer: { text: 'WhatsApp Business API' },
        timestamp: new Date().toISOString(),
      },
    ],
  }
}

// Send alerts to Slack and Discord
async function sendAlerts(event) {
  const promises = []

  if (config.slackWebhookUrl) {
    promises.push(
      axios
        .post(config.slackWebhookUrl, formatSlackMessage(event))
        .catch((err) => console.error('Error sending to Slack:', err.message))
    )
  }

  if (config.discordWebhookUrl) {
    promises.push(
      axios
        .post(config.discordWebhookUrl, formatDiscordMessage(event))
        .catch((err) => console.error('Error sending to Discord:', err.message))
    )
  }

  await Promise.allSettled(promises)
}

// Webhook verification endpoint
app.get('/webhooks/whatsapp', (req, res) => {
  const mode = req.query['hub.mode']
  const token = req.query['hub.verify_token']
  const challenge = req.query['hub.challenge']

  if (mode === 'subscribe' && token === config.verifyToken) {
    console.log('Webhook verified successfully')
    return res.status(200).send(challenge)
  }

  res.sendStatus(403)
})

// Event receiving endpoint
app.post('/webhooks/whatsapp', async (req, res) => {
  // Validate signature in production
  if (config.appSecret && !validateSignature(req)) {
    console.warn('Invalid signature received')
    return res.sendStatus(403)
  }

  // Respond 200 immediately (Meta requires fast response)
  res.sendStatus(200)

  // Process in background
  try {
    const { entry } = req.body

    for (const ent of entry || []) {
      for (const change of ent.changes || []) {
        if (change.field === 'message_template_status_update') {
          const event = change.value
          console.log('Template status update:', JSON.stringify(event, null, 2))

          // Alert only for non-trivial events
          const alertEvents = ['REJECTED', 'FLAGGED', 'DISABLED', 'REINSTATED']
          if (alertEvents.includes(event.event)) {
            await sendAlerts(event)
          }
        }
      }
    }
  } catch (error) {
    console.error('Error processing webhook:', error)
  }
})

app.listen(3000, () => {
  console.log('Webhook server running on port 3000')
})

Required Environment Variables

WHATSAPP_VERIFY_TOKEN=your_verify_token
WHATSAPP_APP_SECRET=your_meta_app_secret
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY

How to Appeal a Rejection

If a template was rejected and you disagree with the reason, you can appeal. The process is via API:

curl -X POST \
  "https://graph.facebook.com/v21.0/TEMPLATE_ID" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message_send_ttl_seconds": -1
  }'

In practice, the most efficient appeal is resubmitting the corrected template. The reason: the formal appeal process can take days and rarely reverses rejections for genuinely problematic content.

The most effective strategy:

  1. Read the rejection_reason — even if generic, it points in a direction
  2. Review the content based on Meta's template guidelines
  3. Fix and resubmit — no need to create a new template (just edit the existing one)
  4. If rejection reason is NONE — try simplifying the template as much as possible and resubmit

Important: You can edit a rejected template. Call POST /TEMPLATE_ID with the corrected components JSON. The template goes back to PENDING and goes through review again.

Editing a Rejected Template

curl -X POST \
  "https://graph.facebook.com/v21.0/TEMPLATE_ID" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "components": [
      {
        "type": "BODY",
        "text": "Hi, {{1}}! Your {{2}} appointment is confirmed for {{3}}. To reschedule, visit: {{4}}",
        "example": {
          "body_text": [["Ana Lima", "medical consultation", "March 15, 2026 at 2:00 PM", "https://appointments.example.com/reschedule"]]
        }
      },
      {
        "type": "FOOTER",
        "text": "Reply CANCEL to cancel your appointment."
      }
    ]
  }'

How to Recover a Paused Template

When a template is PAUSED, you have three paths:

1. Wait for Automatic Recovery

If the block rate naturally drops (because you stopped sending or improved targeting), Meta may automatically reactivate the template. The REINSTATED event arrives via webhook.

This can take anywhere from 24 hours to 7 days depending on the previous volume.

2. Edit the Template

Editing the content moves the template back to PENDING. After approval, the quality history is partially reset — the template starts with a cleaner score.

This is the best option when the problem is the content itself (too aggressive language, implied frequency, etc.).

async function resumeTemplate(templateId, newComponents) {
  const response = await axios.post(
    `https://graph.facebook.com/v21.0/${templateId}`,
    { components: newComponents },
    {
      headers: {
        Authorization: `Bearer ${process.env.WHATSAPP_TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  )
  // Status goes back to PENDING after editing
  console.log('Template sent for review:', response.data)
  return response.data
}

3. Delete and Recreate

Nuclear option — use only if the template needs a new name or the situation is irreversible. Remember that the name stays blocked for 30 days after deletion.

Restructuring Content to Improve Rating

The most common cause of paused templates is content being perceived as spam by recipients. Here's what actually works:

Segment Your Audience

Sending to the entire base at once is a recipe for high block rates. Users who have been inactive for months tend to block when they receive unexpected messages.

Before: Blast to 50,000 contacts at once.
After: Send in batches of 5,000, prioritizing contacts active in the past 30 days.

Actually Personalize Variables

Templates with generic variables are indistinguishable from spam. Use real data that makes sense for that specific user.

❌ "Hi, customer! We have a special offer for you."
✅ "Hi, {{1}}! We noticed you bought {{2}} 30 days ago. Time to restock?"

Add Context for Why You're Reaching Out

Users block when they don't understand why they're receiving that message.

❌ "Get it now: 20% OFF on all products."
✅ "You shopped with us on {{1}} and opted into promotion alerts. Today's deal: {{2}}"

Honor Opt-Outs

Including cancellation instructions isn't just good practice — it's mandatory for marketing. And more importantly: honor opt-outs immediately. Sending to people who asked to be removed is the fastest way to accumulate blocks.

Reduce Frequency

More than one marketing message per week to the same user is dangerous territory. For transactional notifications, the limit is higher, but it still exists.

Operational Summary

The template lifecycle is continuous — it doesn't end at approval. The most critical points to maintain the health of your production templates:

  1. Configure the message_template_status_update webhook — it's the only way to know in real time when something changes
  2. Monitor the number's quality rating weekly — don't wait for the rating to turn RED before acting
  3. Segment before sending — prefer smaller, more qualified audiences over full lists
  4. Edit problematic templates instead of deleting — preserves history and doesn't block the name
  5. For rejections with reason: "NONE" — simplify the content and resubmit

With proactive monitoring and attention to recipient behavior, most quality problems are preventable before they reach PAUSED.

Artigos Relacionados

  • APIs & Integrações

    Template WhatsApp Rejeitado ou Pausado: Como Entender e Corrigir

  • APIs & Integrações

    Como Criar e Gerenciar Templates na WhatsApp Business API: Guia Completo

  • APIs & Integrações

    Como Enviar Imagens, Vídeos e Documentos pela WhatsApp Business API

.