Skip to content
PaalChat Docs

Common workflows

Sending messages

Templates, free-form text, the 24-hour window and references that make retries safe.

.md

In a nutshell

POST a message with your own reference. Templates can be sent any time; free-form text only within 24 hours of the customer's last message. 202 means queued, 200 means that reference was already sent.

Templates or text?

Template Text
When Any time - notifications, reminders, receipts Only within 24 hours of the customer's last message to the business
Content An APPROVED template plus parameters Up to 4096 characters
Outside the window Allowed 422 outside_service_window

The 24-hour window is Meta's customer service window. PaalChat checks it before Meta does, from the last message the customer sent to that business.

The request

POST /businesses/{external_id}/messages

Field Required Notes
to yes International digits, optional +: +233241234567
type yes text or template
text.body for text Max 4096 characters. text.preview_url shows a link preview
template.name, template.language for template An APPROVED template of the business, e.g. fees_reminder / en_US
template.components no Parameters in Meta's format - see Templates
reference strongly recommended Your own ID for the message - see below
phone_number_id only with several numbers Which number to send from
curl -X POST https://whatsapp.paaltech.org/api/v1/businesses/presec/messages \
  -H "Authorization: Bearer $PAALCHAT_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"to": "233241234567", "type": "text", "reference": "inbox-msg-991",
       "text": {"body": "Yes, Kofi can collect his report on Friday."}}'

The response

202 Accepted - a new message, queued:

JSON
{
  "data": {
    "id": 3, "reference": "inbox-msg-991", "wamid": null, "direction": "outgoing",
    "contact": "233241234567", "phone_number_id": "106540352242922", "type": "text",
    "template_name": null, "status": "queued", "error": null, "pricing": null,
    "created_at": "2026-09-30T18:54:28+00:00", "sent_at": null, "delivered_at": null, "read_at": null, "failed_at": null
  }
}

200 OK - this reference was already submitted for this business. You get the original message with its current status, and nothing is sent again.

Always send a reference

The reference is your own ID for the message (for example your database row ID). The same reference for the same business is always the same message, however many times you submit it. That makes every retry safe - see Idempotency.

What is checked before queueing

Check Error
The business is active 409 business_suspended
It has a connected number 409 not_connected
Sending is not paused by PaalChat operators 409 connection_paused
phone_number_id (if given) is connected for this business 404 unknown_phone_number
One number, or phone_number_id given 422 phone_number_required
Text: the customer messaged this number in the last 24 hours (the window is per number) 422 outside_service_window
Template: exists for that language 422 unknown_template
Template: is APPROVED 422 template_not_approved

A queued message can still fail at Meta - for example if the number is not on WhatsApp. That arrives as a message.status callback with status: failed and Meta's error.

Tracking delivery

Text
 queued ──▶ sent ──▶ delivered ──▶ read
   │          │          │
   │          └──────────┴──▶ failed      (never after read; failed is final)
   ├────────────────────────▶ failed
   └──▶ unknown ──▶ sent / delivered / read / failed   (only if Meta reports it)

Statuses only move forward. If delivered arrives after read, keep read.

unknown means PaalChat sent the message to Meta but Meta's answer was lost (a timeout or a Meta server error). The message may or may not have reached the customer, so PaalChat never sends it again by itself. If Meta did deliver it, a sent, delivered or read status still arrives and the message moves on. If it stays unknown, decide in your app: resending with a new reference can duplicate the message, so for anything important check with the customer first. Show ticks from message.status callbacks; use GET .../messages/{id} only if you cannot receive callbacks.

Every message costs the business money at Meta, including replies inside the 24-hour window from 1 October 2026. See WhatsApp pricing.

Several numbers

If a business has more than one connected number, list them with GET .../whatsapp/phones, let the customer pick a default, and pass its phone_number_id on every send.