When to Use
- Product showcases: A retail workflow sends a product image to a customer who asked about an item via WhatsApp, pairing the photo with a price and "Add to Cart" button in a follow-up message.
- Invoice delivery: An accounts receivable workflow generates a PNG invoice and sends it directly to the client's WhatsApp so they can view and pay without needing to open an email.
- Visual order confirmations: An e-commerce workflow sends a photo of the packed order alongside the shipment details, increasing customer confidence in the fulfilment process.
- Real estate listings: A property platform sends the primary listing photo to a prospective buyer who enquired via WhatsApp, with the address and price in the caption.
- Damage report documentation: An insurance workflow sends a photo received via webhook back to the claims team's WhatsApp group, confirming receipt with a reference number in the caption.
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, e.g. v18.0. 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 image file. Required if mediaId is not provided. Supported formats: JPEG, PNG, WebP. Maximum size: 5 MB. |
mediaId | Required* | ID of a previously uploaded image from media/upload. Use instead of mediaUrl when the same image is sent repeatedly or when the URL is not publicly accessible. |
caption | Optional | Text caption displayed below the image. Maximum 1024 characters. |
* Provide either mediaUrl or mediaId — exactly one is required.
Media ID vs URL: When you send the same image to many recipients, upload it once with media/upload and reuse the mediaId. This avoids repeated downloads by Meta's servers and gives more reliable delivery when the source URL may have rate limits or availability issues.
Sample Configuration
{
"resource": "message",
"operation": "sendImage",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"mediaUrl": "https://cdn.example.com/products/{{ $input.productSku }}.jpg",
"caption": "{{ $input.productName }} — ${{ $input.price }}\n\nReply DETAILS for full specifications."
}
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 mediaId and mediaUrl are empty. Provide exactly one. |
131053 (Meta) | Media download failed — the URL returned a non-200 response or the file is inaccessible. |
131052 (Meta) | Media type is not supported or file exceeds the 5 MB size limit. |
131047 (Meta) | 24-hour window expired. Use message/sendTemplate with an approved image template header. |
Output
Success Port
| Field | Type | Description |
messageId | string | Unique WhatsApp message ID (wamid). Retain for downstream operations. |
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.HBgLMTQxNTU1NTI2NzEVAgARGBI5RTI4QUY4NjhFMEI4NDgAA",
"phoneNumberId": "123456789012345",
"to": "14155552671",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"messaging_product\":\"whatsapp\",\"contacts\":[{\"input\":\"14155552671\",\"wa_id\":\"14155552671\"}],\"messages\":[{\"id\":\"wamid.HBgLMTQxNTU1NTI2NzEVAgARGBI5RTI4QUY4NjhFMEI4NDgAA\"}]}"
}
Expression Reference
| Value | Expression | Notes |
| Message ID | {{ $output.sendImg.messageId }} | Use to send a reaction to this image or delete it later. |
| Recipient | {{ $output.sendImg.to }} | Confirmed recipient number for audit logging. |
| Status | {{ $output.sendImg.status }} | Branch on "sent" vs "failed" in an IfCondition node. |
| Error code | {{ $output.sendImg.errorCode }} | Log to a database node 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 it in configuration. |
| Media format compliance | Only send JPEG, PNG, or WebP images. Unsupported formats will be rejected by Meta with a 131052 error. Convert images to JPEG in a Function node if the source format is unknown. |
| File size limit | Maximum image size is 5 MB. Resize or compress images before sending to avoid delivery failures and ensure fast loading on mobile connections. |
| URL accessibility | When using mediaUrl, ensure the URL is publicly accessible without authentication and returns a correct Content-Type header. Signed URLs must remain valid long enough for Meta to download the file. |
| 24-hour window | Image messages are free-form and subject to the 24-hour customer service window. Outside the window, use an approved template with an image header component. |
| Content policy | Do not send images that violate Meta's Commerce Policy or WhatsApp Business Policy, including adult content, violence, or regulated goods without proper approval. |
| Error port handling | Always connect the error port. Media download failures (131053) are common when URLs expire or CDNs block Meta's servers. Route to a retry or fallback node. |
Examples
Product Image with Price Caption
{
"resource": "message",
"operation": "sendImage",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.buyerPhone }}",
"mediaUrl": "https://cdn.example.com/catalog/{{ $input.sku }}/primary.jpg",
"caption": "{{ $input.productName }}\nPrice: ${{ $input.price }}\nSKU: {{ $input.sku }}\n\nReply ORDER to purchase."
}
Sends the primary product image with a structured caption. A follow-up message/sendInteractive node adds purchase buttons immediately after.
Generated Invoice Image via Media ID
{
"resource": "message",
"operation": "sendImage",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.clientPhone }}",
"mediaId": "{{ $output.uploadInvoice.mediaId }}",
"caption": "Invoice #{{ $input.invoiceNumber }} — Due: {{ $input.dueDate }}\nAmount: ${{ $input.amount }}"
}
The workflow first renders the invoice as a PNG using a Function node, then uploads it via media/upload. The returned mediaId is passed here, avoiding repeated uploads when sending the same invoice to multiple contacts.
Real Estate Listing Photo
{
"resource": "message",
"operation": "sendImage",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.enquirerPhone }}",
"mediaUrl": "{{ $input.listingPhotoUrl }}",
"caption": "{{ $input.address }}\n{{ $input.bedrooms }} bed / {{ $input.bathrooms }} bath — {{ $input.area }} sqft\nAsking: ${{ $input.price }}\n\nBook a viewing: {{ $input.bookingUrl }}"
}
A property platform automatically responds to WhatsApp enquiries with the primary listing image and key details. The enquiry is received via a WebhookTrigger node which extracts the listing ID and populates the input variables.