When to Use
- Order status updates: An e-commerce workflow sends a plain-text confirmation to the customer's WhatsApp number the moment their order ships, including the tracking number and estimated delivery date.
- OTP and verification codes: An authentication workflow sends a one-time passcode to a user's WhatsApp number as a fast, high-deliverability alternative to SMS.
- Appointment reminders: A scheduling workflow sends a reminder 24 hours and 1 hour before a booking, including the appointment time, location, and a link to reschedule.
- Customer support replies: A support ticketing workflow sends a text reply to the customer on WhatsApp when an agent updates their ticket status.
- Internal team alerts: A monitoring workflow sends a text alert to a manager's WhatsApp when a critical threshold is crossed, such as a payment gateway error rate exceeding 5%.
Configuration
Connection
| Field | Required | Description |
accessToken | Required | Permanent System User access token from Meta Business Manager. Store in BizFirst Credentials Manager and reference via {{ $credentials.whatsAppToken }}. |
phoneNumberId | Required | Numeric string ID of the sending WhatsApp Business phone number (e.g. 123456789012345). |
apiVersion | Optional | Meta Graph API version, e.g. v18.0. Defaults to v18.0. |
Operation
| Field | Required | Description |
to | Required | Recipient's WhatsApp phone number with country code, no + prefix (e.g. 14155552671). Must be a registered WhatsApp user. |
text | Required | The message body text. Maximum 4096 characters. Plain text only — WhatsApp does not support markdown or HTML in text messages. |
previewUrl | Optional | Set to true to enable URL preview cards when the text contains a URL. Default is false. |
Sample Configuration
{
"resource": "message",
"operation": "sendText",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"text": "Hi {{ $input.customerName }}, your order #{{ $input.orderId }} has shipped! Track it here: https://track.example.com/{{ $input.trackingCode }}",
"previewUrl": "true"
}
Validation Errors
| Error | Cause |
accessToken is required | The accessToken field is empty or missing. |
phoneNumberId is required | The phoneNumberId field is empty or missing. |
to is required | The to field is empty or missing. |
text is required | The text field is empty or missing. |
131026 (Meta) | Recipient phone number is not a valid WhatsApp account. |
131047 (Meta) | Re-engagement message — the 24-hour customer service window has expired. Use message/sendTemplate instead. |
131056 (Meta) | Rate limit exceeded for this phone number. Implement exponential backoff and retry. |
Output
Success Port
Fires after Meta confirms the message was accepted for delivery. The messageId is the unique WhatsApp message ID (wamid) and should be stored if you need to send a reaction or delete the message later.
| Field | Type | Description |
messageId | string | Unique WhatsApp message ID (e.g. wamid.HBgLMTQxNTU1NTI2NzE...). Retain this for reaction, deletion, or tracking purposes. |
phoneNumberId | string | The sending phone number ID used for this message. |
to | string | The recipient phone number as provided. |
status | string | "sent" on success. |
errorCode | string | Empty on success. |
errorMessage | string | Empty on success. |
rawResponse | string | Full raw JSON response from the Meta API. |
Error Port
Fires when Meta rejects the request or when the network call fails. Always connect the error port to a logging or alerting node in production workflows.
| 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 for diagnostics. |
Sample Output
{
"messageId": "wamid.HBgLMTQxNTU1NTI2NzEVAgARGBI2QzM4NjhGQTExMjQ3RDMAA",
"phoneNumberId": "123456789012345",
"to": "14155552671",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"messaging_product\":\"whatsapp\",\"contacts\":[{\"input\":\"14155552671\",\"wa_id\":\"14155552671\"}],\"messages\":[{\"id\":\"wamid.HBgLMTQxNTU1NTI2NzEVAgARGBI2QzM4NjhGQTExMjQ3RDMAA\"}]}"
}
Expression Reference
| Value | Expression | Notes |
| Message ID | {{ $output.sendMsg.messageId }} | Store this to send a reaction or delete the message in a later node. |
| Recipient number | {{ $output.sendMsg.to }} | Echoes back the to value supplied. Use for confirmation logging. |
| Status | {{ $output.sendMsg.status }} | "sent" on success. Use in an IfCondition node to branch on outcome. |
| Error code | {{ $output.sendMsg.errorCode }} | Non-null on error port. Log to a database or monitoring node. |
| Full API response | {{ $output.sendMsg.rawResponse }} | Raw JSON string. Parse with a JsonTransform node for advanced field access. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential storage | Always store the access token in BizFirst Credentials Manager. Reference it via {{ $credentials.whatsAppToken }}. Never paste the raw token into node configuration. |
| 24-hour messaging window | Text messages may only be sent within 24 hours of the recipient's last inbound message. For outbound-only or marketing messages, use message/sendTemplate with a Meta-approved template. |
| Rate limits | Default Tier 1 allows 1,000 unique recipients per 24 hours. In bulk workflows, pace sends through a Delay node and handle 429 errors with retry logic. |
| Phone number format | The to field must not contain a + prefix, spaces, or dashes. Always sanitise the number in a Function or DataMapping node before passing it to this operation. |
| Opt-in compliance | Only send messages to users who have explicitly opted in to receive WhatsApp communications from your business. Store opt-in records and check them before including a number in any send workflow. |
| Error port handling | Always connect the error port. Unhandled 131047 errors (expired window) silently drop messages that should have been sent as templates instead. |
| PII protection | Do not include sensitive personal data, passwords, or payment card numbers in message text. WhatsApp messages are end-to-end encrypted to the device but may be logged server-side in your BizFirstAI workflow audit trail. |
Examples
OTP Verification Code
{
"resource": "message",
"operation": "sendText",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.phoneNumber }}",
"text": "Your BizFirstAI verification code is: {{ $input.otpCode }}\n\nThis code expires in 10 minutes. Do not share it with anyone.",
"previewUrl": "false"
}
Sends a one-time passcode to the user's WhatsApp number during account verification. The workflow upstream generates a 6-digit code, stores it in the session with a 10-minute TTL, and passes it to this node via $input.otpCode.
Shipment Tracking Alert with Link Preview
{
"resource": "message",
"operation": "sendText",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"text": "Your order #{{ $input.orderId }} is on its way! Track your delivery: https://track.example.com/{{ $input.trackingId }}",
"previewUrl": "true"
}
The previewUrl: true option causes WhatsApp to fetch and display a rich link card for the tracking URL, showing the page title and thumbnail. This increases click-through rate compared to a plain URL.
Dynamic Appointment Reminder
{
"resource": "message",
"operation": "sendText",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.patientPhone }}",
"text": "Reminder: You have an appointment with {{ $input.doctorName }} tomorrow at {{ $input.appointmentTime }}.\n\nLocation: {{ $input.clinicAddress }}\n\nReply YES to confirm or call {{ $input.clinicPhone }} to reschedule.",
"previewUrl": "false"
}
A ScheduledTrigger node fires this workflow 24 hours before each appointment. Patient data is sourced from the CRM and passed as input variables. The free-form text format is valid here because appointment reminders fall within the 24-hour conversation window after the patient originally booked.