Pre-approval required: Templates must be submitted via template/create and approved by Meta before use. Using a template that is not in APPROVED status will result in a Meta API error. Rejected or PAUSED templates cannot be sent. Use template/getMany to check template status before deploying a workflow.
When to Use
- Outbound marketing campaigns: A promotional workflow sends an approved marketing template to opted-in customers announcing a seasonal sale, with personalised first name and discount code variables.
- Appointment reminders outside business hours: A healthcare workflow sends a pre-approved reminder template to patients 48 hours before their appointment, regardless of whether the patient has messaged recently.
- Order status proactive notifications: An e-commerce workflow sends a shipping notification template when an order label is created, re-engaging customers who last messaged more than 24 hours ago.
- Authentication OTP via template: A security workflow sends an authentication template with a one-time code variable to users who request account recovery, ensuring delivery even to cold sessions.
- Subscription renewal alerts: A billing workflow sends a renewal reminder template 7 days and 1 day before a subscription expires, with the plan name, renewal date, and price as variables.
Configuration
Connection
| Field | Required | Description |
accessToken | Required | Permanent System User access token. Store in BizFirst Credentials Manager. |
phoneNumberId | Required | Numeric string ID of the sending WhatsApp Business phone number. |
apiVersion | Optional | Meta Graph API version. Defaults to v18.0. |
Operation
| Field | Required | Description |
to | Required | Recipient's phone number with country code, no + prefix (e.g. 14155552671). |
templateName | Required | Exact name of the approved template as registered in Meta Business Manager (e.g. order_shipped). Names are lowercase with underscores. |
languageCode | Required | BCP-47 language code for the template (e.g. en_US, es_MX, fr). Must match the language registered for this template in Meta. |
componentsJson | Optional | JSON array string of component objects that supply variable values to the template's {{1}}, {{2}} placeholders. Omit if the template has no variables. See schema below. |
Components JSON Schema
The componentsJson array contains objects for each component type in the template:
[
{
"type": "header",
"parameters": [
{ "type": "text", "text": "Order #ORD-12345" }
]
},
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Jane" },
{ "type": "text", "text": "ORD-12345" },
{ "type": "text", "text": "3-5 business days" }
]
},
{
"type": "button",
"sub_type": "url",
"index": "0",
"parameters": [
{ "type": "text", "text": "ORD-12345" }
]
}
]
Sample Configuration
{
"resource": "message",
"operation": "sendTemplate",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"templateName": "order_shipped",
"languageCode": "en_US",
"componentsJson": "[{\"type\":\"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.customerName }}\"},{\"type\":\"text\",\"text\":\"{{ $input.orderId }}\"},{\"type\":\"text\",\"text\":\"{{ $input.estimatedDelivery }}\"}]}]"
}
Validation Errors
| Error | Cause |
accessToken is required | The accessToken field is empty. |
phoneNumberId is required | The phoneNumberId field is empty. |
to is required | The to field is empty. |
templateName is required | The templateName field is empty. |
languageCode is required | The languageCode field is empty. |
Invalid JSON in template components | The componentsJson string is malformed JSON. |
132000 (Meta) | Template name not found — the template does not exist or is not APPROVED. |
132001 (Meta) | Template PAUSED or REJECTED — it cannot be sent until reinstated by Meta. |
132012 (Meta) | Template body parameters count does not match the template's variable count. |
132015 (Meta) | Template is in PENDING status — not yet approved by Meta. Wait for approval. |
Output
Success Port
| Field | Type | Description |
messageId | string | Unique WhatsApp message ID (wamid). Template messages delivered to the user open a new 24-hour conversation window. |
phoneNumberId | string | The sending phone number ID. |
to | string | The recipient phone number. |
status | string | "sent" on success. |
errorCode | string | Empty on success. |
errorMessage | string | Empty on success. |
rawResponse | string | Full raw JSON from the Meta API. |
Error Port
| Field | Type | Description |
status | string | "failed" or "error". |
errorCode | string | Meta error code or BizFirst validation code. |
errorMessage | string | Human-readable error description. |
rawResponse | string | Raw API error response. |
Sample Output
{
"messageId": "wamid.HBgLMTQxNTU1NTI2NzEVAgARGBIzQzM4NjhGQTExMjQ3RCMAA",
"phoneNumberId": "123456789012345",
"to": "14155552671",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"messaging_product\":\"whatsapp\",\"contacts\":[{\"input\":\"14155552671\",\"wa_id\":\"14155552671\"}],\"messages\":[{\"id\":\"wamid.HBgLMTQxNTU1NTI2NzEVAgARGBIzQzM4NjhGQTExMjQ3RCMAA\"}]}"
}
Expression Reference
| Value | Expression | Notes |
| Message ID | {{ $output.sendTemplate.messageId }} | Store this. Delivery of a template message opens a new 24-hour window — you can then follow up with free-form messages. |
| Recipient | {{ $output.sendTemplate.to }} | For delivery confirmation audit logging. |
| Status | {{ $output.sendTemplate.status }} | Branch on "sent" vs "failed" in an IfCondition node. |
| Error code | {{ $output.sendTemplate.errorCode }} | Log on the error port. 132000 means the template name is wrong; 132015 means it is still pending Meta approval. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential storage | Store the access token in BizFirst Credentials Manager. Never inline in configuration. |
| Template status check | Before deploying a workflow that uses a template, verify the template is in APPROVED status using template/get. Add a startup check to your workflow that fails fast if the template is not APPROVED. |
| Variable count matching | The number of parameters in each componentsJson component must exactly match the number of {{N}} placeholders in the template. A mismatch causes a 132012 error. Validate parameter counts in a Function node. |
| Language code accuracy | The languageCode must match exactly what was registered in Meta — including the locale suffix (e.g. en_US vs en_GB). A wrong language code returns error 132000. |
| Marketing opt-in | Marketing category templates may only be sent to users who have explicitly opted in to marketing messages. Document and verify opt-in status before including any number in a marketing template campaign. |
| Template naming | Store template names and language codes in workflow configuration variables or a database node, not hardcoded in the node. This allows template updates without workflow republishing. |
| Error port | Always connect the error port. Template errors (132000, 132001, 132012) require different remediation actions and should not be silently swallowed. |
Examples
Shipping Notification — Order Shipped Template
{
"resource": "message",
"operation": "sendTemplate",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"templateName": "order_shipped",
"languageCode": "en_US",
"componentsJson": "[{\"type\":\"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.customerName }}\"},{\"type\":\"text\",\"text\":\"{{ $input.orderId }}\"},{\"type\":\"text\",\"text\":\"{{ $input.trackingNumber }}\"},{\"type\":\"text\",\"text\":\"{{ $input.carrier }}\"}]},{\"type\":\"button\",\"sub_type\":\"url\",\"index\":\"0\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.trackingNumber }}\"}]}]"
}
Triggered by an order management system webhook when a shipping label is created. The template body substitutes the customer name, order ID, tracking number, and carrier. The URL button in the template appends the tracking number to a tracking page URL.
Appointment Reminder — Healthcare Template
{
"resource": "message",
"operation": "sendTemplate",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.patientPhone }}",
"templateName": "appointment_reminder",
"languageCode": "en_US",
"componentsJson": "[{\"type\":\"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.patientName }}\"},{\"type\":\"text\",\"text\":\"{{ $input.doctorName }}\"},{\"type\":\"text\",\"text\":\"{{ $input.appointmentDate }}\"},{\"type\":\"text\",\"text\":\"{{ $input.appointmentTime }}\"}]}]"
}
A ScheduledTrigger fires 48 hours before each appointment. Because this is an outbound proactive notification that may go to patients who have not messaged recently, the template type (UTILITY) ensures delivery compliance. The 24-hour window opens, allowing follow-up messages if the patient replies.
Authentication OTP Template
{
"resource": "message",
"operation": "sendTemplate",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.userPhone }}",
"templateName": "auth_otp_code",
"languageCode": "en_US",
"componentsJson": "[{\"type\":\"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.otpCode }}\"}]},{\"type\":\"button\",\"sub_type\":\"url\",\"index\":\"0\",\"parameters\":[{\"type\":\"text\",\"text\":\"{{ $input.otpCode }}\"}]}]"
}
A AUTHENTICATION category template registered with Meta for OTP delivery. The OTP code is substituted into both the body text and the button URL suffix (which deep-links back into the app). Authentication templates receive the highest delivery priority from Meta.