When to Use
- Invoice and receipt delivery: An accounts workflow sends a PDF invoice directly to the client's WhatsApp the moment payment is due, providing a zero-friction path to payment confirmation.
- Contract distribution: A sales workflow sends a PDF contract to the prospect via WhatsApp for quick review before a scheduled signing call, bypassing email delays.
- Compliance document delivery: A regulatory workflow sends mandatory disclosure documents to customers at account opening, with the document reference in the caption for audit trail purposes.
- Report sharing: A BI workflow sends a weekly Excel or PDF report to a manager's WhatsApp at a scheduled time, ensuring the report is seen even when email is not monitored.
- Onboarding material dispatch: An HR workflow sends the employee handbook, IT setup guide, and policy documents to a new hire's WhatsApp on their first day.
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). |
mediaUrl | Required* | Publicly accessible HTTPS URL to the document. Supported formats: PDF, DOCX, XLSX, PPTX, TXT, and many others. Maximum size: 100 MB. |
mediaId | Required* | ID of a previously uploaded document from media/upload. Use for private documents or repeated sends. |
filename | Optional | The filename displayed in WhatsApp (e.g. Invoice_2024_001.pdf). If omitted, WhatsApp uses a generic name. Always specify for professional appearance. |
caption | Optional | Text displayed below the document attachment. Maximum 1024 characters. |
* Provide either mediaUrl or mediaId — exactly one is required.
Sample Configuration
{
"resource": "message",
"operation": "sendDocument",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.clientPhone }}",
"mediaUrl": "https://docs.example.com/invoices/{{ $input.invoiceId }}.pdf",
"filename": "Invoice_{{ $input.invoiceId }}.pdf",
"caption": "Invoice #{{ $input.invoiceId }} | Due: {{ $input.dueDate }} | Amount: ${{ $input.amount }}\n\nPlease confirm receipt by replying RECEIVED."
}
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. |
Either mediaId or mediaUrl is required | Both fields are empty. Provide exactly one. |
131053 (Meta) | Document download failed — URL inaccessible or returned a non-200 response. |
131052 (Meta) | Unsupported document format or file exceeds 100 MB. |
131047 (Meta) | 24-hour window expired. Use a template with a document header component. |
Output
Success Port
| Field | Type | Description |
messageId | string | Unique WhatsApp message ID (wamid). |
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.HBgLMTQxNTU1NTI2NzEVAgARGBIyRTI4QUY0NjhFMEI4NCMAA",
"phoneNumberId": "123456789012345",
"to": "14155552671",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"messaging_product\":\"whatsapp\",\"contacts\":[{\"input\":\"14155552671\",\"wa_id\":\"14155552671\"}],\"messages\":[{\"id\":\"wamid.HBgLMTQxNTU1NTI2NzEVAgARGBIyRTI4QUY0NjhFMEI4NCMAA\"}]}"
}
Expression Reference
| Value | Expression | Notes |
| Message ID | {{ $output.sendDoc.messageId }} | Retain for audit trail and potential deletion within 60 minutes. |
| Recipient | {{ $output.sendDoc.to }} | For delivery confirmation logging. |
| Status | {{ $output.sendDoc.status }} | Branch on "sent" vs "failed" in IfCondition. |
| Error code | {{ $output.sendDoc.errorCode }} | Log on the error port for failure analysis. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential storage | Store the access token in BizFirst Credentials Manager. Never inline in configuration. |
| Filename convention | Always set the filename field with a descriptive, professional name including the file extension. Default filenames from WhatsApp are generic and confusing for recipients. |
| URL access control | When using mediaUrl, ensure the URL is temporarily public during the Meta download window. If the document is sensitive, pre-sign the URL with a minimum 60-second expiry, or use media/upload and pass the mediaId. |
| Sensitive documents | Do not send documents containing unencrypted PII (e.g. social security numbers, full credit card numbers). WhatsApp provides end-to-end encryption but documents may be saved to device storage or forwarded by recipients. |
| 24-hour window | Document messages are free-form and subject to the 24-hour customer service window. For outbound-only document delivery (e.g. monthly statements), use a template with a document header. |
| Error port | Always connect the error port. URL access failures are common for documents hosted behind authenticated portals. |
Examples
Automated Invoice Delivery
{
"resource": "message",
"operation": "sendDocument",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.clientPhone }}",
"mediaUrl": "https://storage.example.com/invoices/{{ $input.invoiceNumber }}.pdf",
"filename": "Invoice_{{ $input.invoiceNumber }}_{{ $input.period }}.pdf",
"caption": "Your invoice for {{ $input.period }} is attached.\nAmount due: ${{ $input.amount }}\nDue date: {{ $input.dueDate }}\n\nPay online: {{ $input.paymentUrl }}"
}
A billing workflow triggers this node when an invoice is generated. The URL is a presigned S3 link valid for 5 minutes — sufficient for Meta to download it. The caption includes a direct payment link to reduce the steps to payment.
Employee Handbook on Onboarding
{
"resource": "message",
"operation": "sendDocument",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.employeePhone }}",
"mediaId": "{{ $config.employeeHandbookMediaId }}",
"filename": "BizFirst_Employee_Handbook_2024.pdf",
"caption": "Welcome to BizFirst, {{ $input.employeeName }}! Your employee handbook is attached. Please read Section 3 (IT Policy) before your first meeting. Reply READ when done."
}
The handbook is uploaded once at the start of each year via media/upload, and the resulting mediaId is stored in workflow configuration. All new hire onboarding workflows reuse the same mediaId, avoiding repeated uploads.
Weekly Report Distribution
{
"resource": "message",
"operation": "sendDocument",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.managerPhone }}",
"mediaUrl": "{{ $output.generateReport.reportUrl }}",
"filename": "Weekly_Report_{{ $input.weekEnding }}.xlsx",
"caption": "Weekly performance report for week ending {{ $input.weekEnding }}.\nKey metrics: Revenue ${{ $input.revenue }} | Orders {{ $input.orderCount }} | NPS {{ $input.nps }}"
}
A ScheduledTrigger fires every Friday at 5 PM. A Function node generates the Excel report, uploads it to S3, and returns the presigned URL. This node sends the document with a summary in the caption so the manager can see key numbers without opening the file.