WhatsApp Template Setup Guide (YCloud / Meta)
FlowPOS sends WhatsApp on one platform number through YCloud (Meta Business Solution Provider). SMS stays on Twilio. Landing wa.me CTAs and Chatwoot are unchanged.
There are no per-tenant WABAs and no inbound WhatsApp inbox in v1. HTTP 200 on POST /v2/whatsapp/messages means the message was accepted (enqueued), not delivered.
Messaging windowβ
- Freeform text only works inside the 24-hour customer-care window after the customer last messaged the business number.
- First contact and anything outside that window must use a Meta-APPROVED utility template.
- Invoice WhatsApp already puts
pdfLinkin the body (signed URL). v1 does not use URL-button suffix variables.
Environmentβ
| Variable | Required | Description |
|---|---|---|
YCLOUD_API_KEY | Yes | Sent as X-API-Key |
YCLOUD_WHATSAPP_FROM | Yes | Platform WhatsApp number, E.164 |
YCLOUD_WEBHOOK_SECRET | Yes in staging/production | HMAC secret for YCloud-Signature |
YCLOUD_WHATSAPP_TEMPLATE_LANGUAGE | No | Default es |
YCLOUD_API_BASE_URL | No | Default https://api.ycloud.com/v2 |
Webhook URL to register in YCloud (subscribe only to whatsapp.message.updated):
https://<API_URL>/webhooks/ycloud/whatsapp
Unknown event types return HTTP 200 and do no work. After a valid signature, FlowPOS still returns 2xx if the database update fails, so YCloud does not suspend the webhook URL.
Meta template names (not Twilio SIDs)β
communication_template.provider_template_id stores the Meta APPROVED template name (for example invoice_whatsapp), not a Twilio Content SID (HXβ¦).
Language comes from YCLOUD_WHATSAPP_TEMPLATE_LANGUAGE (default es). The template must be approved in that language.
Body variables are positionalβ
YCloud/Meta templates use {{1}}, {{2}}, β¦ only. FlowPOS senders still pass named keys (customerName, invoiceNumber, pdfLink). Mapping is an explicit order list β never JavaScript object key order.
Default order (DEFAULT_BODY_VAR_ORDER in ycloud-whatsapp.mappers.ts):
customerNameβ{{1}}invoiceNumberβ{{2}}amountβ{{3}}currencyCodeβ{{4}}saleDateβ{{5}}businessNameβ{{6}}pdfLinkβ{{7}}
invoice_whatsapp and sale_whatsapp use that list. Extra keys are ignored. If you add a new YCloud template, add its order to BODY_VAR_ORDER.
Numeric keys "1", "2" are accepted as a fallback and sorted numerically.
Creating a utility templateβ
- In Meta / YCloud, create a utility template (not marketing) in
esunless you change the env language. - Use positional body placeholders that match
BODY_VAR_ORDER. - Wait for APPROVED.
- Store the template name on the FlowPOS template:
curl --location --request PATCH "https://your-api-url.com/communication-templates/{template-id}" \
--header "Authorization: Bearer YOUR_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"providerTemplateId": "invoice_whatsapp"
}'
UPDATE communication_template
SET provider_template_id = 'invoice_whatsapp'
WHERE code = 'invoice_whatsapp'
AND channel = 'whatsapp';
Invoice body exampleβ
Hola {{1}},
Su factura #{{2}} estΓ‘ lista.
Monto: {{3}} {{4}}
Fecha: {{5}}
{{6}}
PDF: {{7}}
Sendingβ
POST /communications/send with channel: "whatsapp" and a templateId whose providerTemplateId is the Meta name. Named variables are mapped in BODY_VAR_ORDER.
{
"businessId": "your-business-id",
"channel": "whatsapp",
"type": "invoice",
"recipientType": "customer",
"recipientId": "customer-id",
"recipientContact": "+502XXXXXXXX",
"templateId": "template-id-from-flowpos",
"templateVariables": {
"customerName": "Ana",
"invoiceNumber": "INV-001",
"amount": "150.00",
"currencyCode": "GTQ",
"saleDate": "2026-08-26",
"businessName": "Tienda",
"pdfLink": "https://signed-url.example/invoice.pdf"
}
}
Phones are normalized then checked with validateWhatsAppNumber (E.164).
Status mappingβ
| YCloud | FlowPOS |
|---|---|
accepted, sent | sent |
delivered | delivered |
read | opened |
failed | failed |
providerMessageId is the YCloud message id (not wamid). externalId on the enqueue request is the FlowPOS communicationId.
HTTP 429 from YCloud is retried by BullMQ. Other 4xx become success: false on the communication row.
Rate limitβ
Token bucket ycloud:whatsapp: burst 40, refill 4/sec. SMS stays on twilio:sms.
Cutover checklist (ops, not this code change)β
- Staging YCloud API key, from-number, and webhook secret.
- One APPROVED
esutility template whose body vars matchBODY_VAR_ORDER. - Rewrite
provider_template_idoff any leftover TwilioHXβ¦SIDs. - Register
https://<API_URL>/webhooks/ycloud/whatsappforwhatsapp.message.updated. - Send a test invoice to a
+502number.