Saltar al contenido principal

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 pdfLink in the body (signed URL). v1 does not use URL-button suffix variables.

Environment​

VariableRequiredDescription
YCLOUD_API_KEYYesSent as X-API-Key
YCLOUD_WHATSAPP_FROMYesPlatform WhatsApp number, E.164
YCLOUD_WEBHOOK_SECRETYes in staging/productionHMAC secret for YCloud-Signature
YCLOUD_WHATSAPP_TEMPLATE_LANGUAGENoDefault es
YCLOUD_API_BASE_URLNoDefault 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):

  1. customerName → {{1}}
  2. invoiceNumber → {{2}}
  3. amount → {{3}}
  4. currencyCode → {{4}}
  5. saleDate → {{5}}
  6. businessName → {{6}}
  7. 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​

  1. In Meta / YCloud, create a utility template (not marketing) in es unless you change the env language.
  2. Use positional body placeholders that match BODY_VAR_ORDER.
  3. Wait for APPROVED.
  4. 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​

YCloudFlowPOS
accepted, sentsent
delivereddelivered
readopened
failedfailed

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)​

  1. Staging YCloud API key, from-number, and webhook secret.
  2. One APPROVED es utility template whose body vars match BODY_VAR_ORDER.
  3. Rewrite provider_template_id off any leftover Twilio HX… SIDs.
  4. Register https://<API_URL>/webhooks/ycloud/whatsapp for whatsapp.message.updated.
  5. Send a test invoice to a +502 number.