Sign inGet started

WhatsApp Template Webhooks

Overview

You can subscribe to webhook events to receive real-time notifications when WhatsApp message templates change. These webhooks notify you when:
  1. A template is created -- A new WhatsApp message template draft is added
  2. A template is updated -- A template's approval status, quality rating, or content changes
  3. A template is deleted -- A template is removed
This is particularly useful for monitoring Meta's template approval workflow and quality rating changes, which directly affect your WhatsApp messaging limits and delivery capabilities.
This page covers WhatsApp-specific template webhook details. For general information about template webhooks (including how to create subscriptions via the UI or API), see Template Webhooks.

Creating a WhatsApp Template Webhook

Template webhooks can be created through the Bird UI or the API. For step-by-step instructions, see Template Webhooks -- Creating a Webhook Subscription.
To receive only WhatsApp template events, add a platformId filter set to whatsapp:
Via the UI:
  1. Go to Developer App > Webhooks > Create webhook
  2. Select Templates as the service
  3. Choose your event (template.created, template.updated, or template.deleted)
  4. Add an event filter with key platformId and value whatsapp
Via the API:
Ejemplo de código
POST /workspaces/{workspaceId}/webhooks

{
  "service": "templates",
  "event": "template.updated",
  "url": "https://your-server.com/webhooks/wa-templates",
  "signingKey": "your-signing-key",
  "eventFilters": [
    {
      "key": "platformId",
      "value": "whatsapp"
    }
  ]
}
If you omit the platformId filter, you will receive template events for all platforms (WhatsApp, SMS, RCS, etc.). Add the filter if you only need WhatsApp events.

WhatsApp Template Lifecycle Events

WhatsApp templates go through a specific lifecycle governed by Meta's approval process. The template.updated event fires at each stage:
StageTemplate statusplatformInfo statusWhat happened
Draft createddraft--Template created, not yet submitted
Submitted for approvalpendingpendingTemplate activated and sent to Meta for review
Approved by MetaactiveactiveMeta approved the template; ready for sending
Rejected by Meta (all)inactiveinactiveMeta rejected the template on all WABAs/locales
Rejected by Meta (partial)activeinactiveMeta rejected on one WABA/locale, but another is still active
Quality rating changedactiveactiveMeta updated the quality rating
Flagged by MetaactiveactiveTemplate flagged due to low quality -- still usable but must improve before deadline or it will be disabled
Paused by Metaactive or inactiveinactiveTemplate paused on this WABA/locale; cannot send until Meta lifts it
Disabled by Metaactive or inactiveinactiveTemplate disabled after failing to improve quality while flagged
Reinstated by MetaactiveactivePreviously flagged/paused/disabled template restored by Meta
In appealactive or inactiveinactiveRejected template is being appealed
Partial status changes: When a template is submitted to multiple WABAs or locales, Meta manages each independently. If one WABA/locale is rejected, paused, or disabled while another remains approved, the overall template status stays active but the affected entry in platformInfo will show inactive. Always check the individual platformInfo entries rather than relying solely on the top-level status field.
Flagged vs. Paused vs. Disabled: A flagged template remains active in platformInfo -- it is a warning state and the template can still be used for sending. In contrast, paused and disabled templates show status: "inactive". To distinguish between paused, disabled, and rejected, use the Get ChannelTemplate endpoint and check the platformStatus field in the approvals array, which contains the detailed Meta status: whatsapp_flagged, whatsapp_paused, whatsapp_disabled, whatsapp_in_appeal, or whatsapp_reinstated.

WhatsApp-Specific Payload Fields

Template webhook payloads include a platformInfo object with WhatsApp-specific state. For WhatsApp, each key follows the format whatsapp:{wabaId}:{locale} -- for example, whatsapp:114128184961630:en-US.

platformInfo fields for WhatsApp

FieldTypeDescription
statusstringApproval status: draft, active, inactive, pending, pendingReview
categorystringWhatsApp category: UTILITY, MARKETING, or AUTHENTICATION
qualityRatingstringQuality rating: GREEN, YELLOW, RED, or UNKNOWN

Example Payloads

Template approved by Meta

Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "active",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "active",
        "category": "MARKETING",
        "qualityRating": "GREEN"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-21T14:30:00.00Z"
  }
}

Quality rating degraded

This is critical to monitor -- quality degradation can lead to template pausing or messaging limit reductions.
Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "active",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "active",
        "category": "MARKETING",
        "qualityRating": "YELLOW"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-25T08:15:00.00Z"
  }
}

Template rejected by Meta (all WABAs)

When Meta rejects the template across all WABAs/locales, the overall template status becomes inactive.
Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "inactive",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "inactive",
        "category": "MARKETING",
        "qualityRating": "UNKNOWN"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-21T16:45:00.00Z"
  }
}

Template rejected by Meta (partial -- one WABA rejected, another still active)

When a template is submitted to multiple WABAs and Meta rejects it on one but not another, the overall template status remains active because there is still at least one active approval. The rejected WABA/locale entry in platformInfo shows inactive while the approved one shows active.
Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "active",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "active",
        "category": "MARKETING",
        "qualityRating": "GREEN"
      },
      "whatsapp:998877665544332:en": {
        "status": "inactive",
        "category": "MARKETING",
        "qualityRating": "UNKNOWN"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-21T16:45:00.00Z"
  }
}
When handling template.updated events, always iterate through the individual platformInfo entries to check for rejections or quality changes. Do not rely only on the top-level status field -- it will remain active as long as at least one WABA/locale is still approved.

Template flagged by Meta (quality warning)

When Meta flags a template due to low quality, the platformInfo entry remains active but the qualityRating changes to RED. The template can still be used for sending, but if quality does not improve before Meta's deadline, it will be paused or disabled.
Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "active",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "active",
        "category": "MARKETING",
        "qualityRating": "RED"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-22T08:15:00.00Z"
  }
}
Note that status is still "active" even with qualityRating: "RED". Flagging is a warning -- the template is usable but at risk. To confirm the exact Meta status, call the Get ChannelTemplate endpoint and check the platformStatus field (e.g., whatsapp_flagged).

Template paused by Meta

When Meta pauses a template, the platformInfo entry changes to inactive. If other WABAs are still active, the overall template status remains active.
Ejemplo de código
{
  "service": "templates",
  "event": "template.updated",
  "payload": {
    "id": "34dcb086-82c7-47f2-8939-7f0057def64e",
    "organizationId": "8a2f4e10-3b5c-4d6e-9f8a-1b2c3d4e5f6a",
    "workspaceId": "7c6d5e4f-3a2b-1c0d-9e8f-7a6b5c4d3e2f",
    "projectId": "11323dfa-121f-4a4e-b4ef-35d325eaacb5",
    "name": "welcome_message",
    "language": "en",
    "status": "active",
    "platformInfo": {
      "whatsapp:114128184961630:en": {
        "status": "active",
        "category": "MARKETING",
        "qualityRating": "GREEN"
      },
      "whatsapp:998877665544332:en": {
        "status": "inactive",
        "category": "MARKETING",
        "qualityRating": "RED"
      }
    },
    "createdAt": "2025-09-20T09:11:46.28Z",
    "updatedAt": "2025-09-22T08:15:00.00Z"
  }
}
In this payload, the second WABA (998877665544332) has been paused or disabled -- visible from status: "inactive". The webhook platformInfo does not distinguish between paused and disabled states. To determine the exact Meta status, call the Get ChannelTemplate endpoint and inspect the platformStatus field in the approvals array (whatsapp_paused or whatsapp_disabled).

Quality Ratings

WhatsApp assigns quality ratings to message templates based on how recipients interact with messages sent using those templates. Quality ratings directly impact your ability to send messages.
RatingDescription
GREENHigh quality. Template is performing well with recipients.
YELLOWMedium quality. Template quality is declining. Consider reviewing template content and targeting.
REDLow quality. Template may be paused by Meta if quality does not improve.
UNKNOWNQuality rating has not yet been assigned (e.g., newly approved template).
When a template's quality rating drops to RED, Meta may automatically pause it, preventing you from sending messages with that template. Set up a template.updated webhook to proactively detect quality changes and take action before templates are paused.
RatingRecommended action
GREENNo action needed. Continue using the template.
YELLOWReview message content, targeting, and opt-in practices. Monitor for further degradation.
REDImmediately review and revise the template. Consider pausing usage until quality improves.
UNKNOWNWait for Meta to assign a rating after sufficient message volume.

WhatsApp Template Status Lifecycle

Overview

WhatsApp templates move through several states as they are created, submitted to Meta for review, and monitored for quality. The webhook platformInfo.status provides a simplified view (active, inactive, pending), while the full details -- including the exact Meta status and rejection reasons -- are available via the Get ChannelTemplate API.

Lifecycle diagram

Ejemplo de código
                                ┌─────────────────────────────┐
                                │          draft               │
                                └──────────┬──────────────────┘
                                           │ activate / submit
                                           ▼
                                ┌─────────────────────────────┐
                                │         pending              │
                                └──────┬──────────────┬───────┘
                            approved   │              │  rejected
                                       ▼              ▼
                     ┌─────────────────────┐   ┌─────────────────────┐
                     │       active         │   │      inactive        │
                     │  (sending allowed)   │   │  (rejected by Meta)  │
                     └──┬──────────────┬───┘   └──────────┬───────────┘
                        │              │                   │
               flagged  │              │ paused/disabled    │ appeal
              (quality  │              │                   ▼
               warning) │              │         ┌─────────────────────┐
                        ▼              ▼         │  inactive            │
             ┌──────────────┐  ┌────────────┐   │  (in appeal)         │
             │   active      │  │  inactive   │   └─────────┬───────────┘
             │  (flagged)    │  │  (paused/   │             │
             │  still usable │  │  disabled)  │    approved  │
             └──┬────────┬──┘  └─────────────┘             │
                │        │                                  ▼
     reinstated │        │ deadline passed          back to active
                ▼        ▼
             active    inactive
           (restored)  (paused → disabled)

Template-level statuses (top-level status)

The top-level status on the webhook payload reflects the overall template state across all WABAs and locales.
StatusCan send?Description
draftNoTemplate created but not yet submitted to Meta
pendingNoTemplate submitted to Meta for review
activeYesApproved by Meta on at least one WABA/locale
inactiveNoRejected, paused, or disabled on all WABAs/locales
pendingReviewNoPending internal Bird approval (when approval flows are configured)
A top-level status of active does not mean all WABAs/locales are healthy. Individual entries in platformInfo may be inactive (rejected, paused, or disabled) while others remain active.

Platform-level statuses (platformInfo.status in webhooks)

The webhook platformInfo only exposes a simplified status per WABA/locale:
platformInfo statusMeaning
activeApproved or flagged (template can still be used for sending)
inactiveRejected, paused, or disabled (cannot send on this WABA)
pendingSubmitted to Meta, awaiting review
Because inactive covers multiple distinct Meta states, use the Get ChannelTemplate API to distinguish between them.

Detailed platform statuses (platformStatus via Get ChannelTemplate API)

The platformStatus field on each approval in the platformContent[].approvals[] array provides the exact Meta status.
platformStatusMaps toCan send?Description
whatsapp_approvedactiveYesApproved by Meta for this WABA/locale
whatsapp_flaggedactiveYesFlagged for low quality; must improve before Meta's deadline or it will be paused
whatsapp_reinstatedactiveYesPreviously flagged/paused/disabled, now restored by Meta
whatsapp_rejectedinactiveNoRejected by Meta during review
whatsapp_pausedinactiveNoTemporarily paused by Meta due to quality issues
whatsapp_disabledinactiveNoDisabled after failing to improve quality while flagged/paused
whatsapp_in_appealinactiveNoRejected template currently under appeal

Getting rejection and status change details

The webhook payload does not include rejection reasons. When you receive a template.updated webhook where a platformInfo entry has status: "inactive", call the Get ChannelTemplate API to retrieve the full approval details:
Ejemplo de código
GET /workspaces/{workspaceId}/projects/{projectId}/channel-templates/{channelTemplateId}
Each approval in the response platformContent[].approvals[] array includes:
FieldTypeDescription
statusstringApproval status: pending, approved, rejected, blocked, deleted
platformStatusstringDetailed Meta status (see table above)
reasonCodestringNormalized rejection reason code (see table below), or null if not rejected
reasonDescriptionstringHuman-readable description from Meta, or null. Especially useful when reasonCode is unknown
Rejection reason codes:
reasonCodeDescription
whatsapp_scamMeta identified the template as a scam attempt
whatsapp_invalid_formatInvalid format (often a duplicate template name on the same WABA)
whatsapp_incorrect_categoryTemplate content does not match the specified category
whatsapp_promotionalRejected due to inappropriate promotional content
whatsapp_tag_content_mismatchSubmitted content does not match the tag
whatsapp_abusive_contentRejected due to abusive or inappropriate content
whatsapp_fallbackRejected due to fallback policy violation
whatsapp_noneNo specific reason provided by Meta; check reasonDescription for details
unknownUnrecognized reason; check reasonDescription for the raw platform reason
Example: approval entry for a rejected template
Ejemplo de código
{
  "approvalReference": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "platformAccountIdentifier": "114128184961630",
  "platform": "whatsapp",
  "status": "rejected",
  "platformStatus": "whatsapp_rejected",
  "reasonCode": "whatsapp_incorrect_category",
  "reasonDescription": "The content of this template does not match the selected category. Please update the category or the content.",
  "channelIds": ["c1d2e3f4-a5b6-7890-cdef-123456789abc"]
}
reasonCode and reasonDescription are also populated for paused and disabled templates (not just rejections). For paused templates, reasonCode is typically whatsapp_none and reasonDescription may contain Meta's explanation of the quality issue.

Complete Example

1. Subscribe to WhatsApp template updates

Ejemplo de código
POST /workspaces/{workspaceId}/webhooks

{
  "service": "templates",
  "event": "template.updated",
  "url": "https://your-server.com/webhooks/wa-templates",
  "signingKey": "my-secret-signing-key",
  "eventFilters": [
    {
      "key": "platformId",
      "value": "whatsapp"
    }
  ]
}

2. Handle incoming webhooks

Your webhook endpoint should:
  1. Verify the signature using the signingKey you provided
  2. Parse the payload and iterate through every platformInfo entry
  3. Detect the state for each WABA/locale and take action:
ConditionAction
All platformInfo entries active, quality GREENTemplate fully approved and healthy -- enable sending on all WABAs
Any entry with qualityRating: "YELLOW" and status: "active"Quality declining -- review template content and targeting before further degradation
Any entry with qualityRating: "RED" and status: "active"Template flagged -- still usable but revise content urgently before Meta pauses it
Any platformInfo entry inactiveWABA paused, disabled, or rejected -- fetch details (see step 3)
Top-level status: "inactive"All WABAs rejected, paused, or disabled -- stop sending; fetch details (see step 3)
Event is template.deletedStop using the template for sending
Do not rely only on the top-level status. A status of active does not guarantee all WABAs/locales are healthy -- always iterate through platformInfo entries individually.

3. Fetch rejection and status details

When any platformInfo entry shows status: "inactive", call the Get ChannelTemplate API to find out exactly what happened and why:
Ejemplo de código
GET /workspaces/{workspaceId}/projects/{projectId}/channel-templates/{channelTemplateId}
In the response, find the matching approval in the platformContent[].approvals[] array by matching the platformAccountIdentifier (WABA ID) and check:
  • platformStatus -- the exact Meta status (whatsapp_rejected, whatsapp_paused, whatsapp_disabled, or whatsapp_in_appeal)
  • reasonCode -- a normalized reason code (e.g., whatsapp_incorrect_category, whatsapp_abusive_content)
  • reasonDescription -- a human-readable explanation from Meta
For the full list of reasonCode values, see Rejection reason codes.
Example response (approval for a rejected WABA):
Ejemplo de código
{
  "platformContent": [
    {
      "locale": "en",
      "platform": "whatsapp",
      "approvals": [
        {
          "approvalReference": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "platformAccountIdentifier": "998877665544332",
          "platform": "whatsapp",
          "status": "rejected",
          "platformStatus": "whatsapp_rejected",
          "reasonCode": "whatsapp_incorrect_category",
          "reasonDescription": "The content of this template does not match the selected category.",
          "channelIds": ["c1d2e3f4-a5b6-7890-cdef-123456789abc"]
        }
      ]
    }
  ]
}
This follow-up call is also useful for paused and disabled templates. The reasonCode and reasonDescription fields are populated for all status changes, not just rejections.

Comparison with Channel Message Events

Template webhooks and channel message webhooks serve different purposes:
Use caseServiceEvent
Template approval/quality/lifecycletemplatestemplate.*
Outgoing message delivery statuschannelswhatsapp.outbound
Incoming message receivedchannelswhatsapp.inbound
Message read/clicked/reactionchannelswhatsapp.interaction
For message delivery tracking, see Message status and interactions.