> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ycloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Messages API best practices

> Build reliable, compliant, and observable WhatsApp message sending workflows for production.

Use these practices to send WhatsApp messages reliably at production scale. You
will choose the right endpoint, correlate each attempt, converge delivery state,
retry safely, enforce consent, and control throughput.

## Before you begin

* Connect and register the WhatsApp business phone numbers that will send messages.
* Store your YCloud API key on the server.
* Configure a signed webhook endpoint for `whatsapp.message.updated`.
* Define how your system records consent, opt-outs, message purpose, and retention.
* Assign owners for sending, webhook processing, and incident response.

## Choose the sending endpoint

Use the queued endpoint by default. Use direct sending only when the application
must know whether WhatsApp accepted the submission before continuing.

| Endpoint | Choose it when | Operational effect |
| - | - | - |
| `POST /whatsapp/messages` | You send notifications, campaigns, or other normal outbound traffic. | YCloud accepts the request and submits it asynchronously. Your application can absorb bursts with its own queue. |
| `POST /whatsapp/messages/sendDirectly` | You send an OTP or another time-sensitive message that requires synchronous submission. | The request waits for submission to the WhatsApp Business API. It does not wait for final delivery. |

A successful response from either endpoint is not proof of delivery. Store the
returned message `id` and use `whatsapp.message.updated` events to learn whether
the message is `sent`, `failed`, `delivered`, or `read`.

<Warning>
  Do not switch an entire high-volume workload to `sendDirectly` to reduce queue
  latency. Synchronous calls hold application resources and still require
  asynchronous status handling.
</Warning>

## Create one internal send record

Create a durable record before calling the API. Give the record a unique
business key, such as an order event ID plus the message purpose. Enforce that
uniqueness in your database so concurrent workers cannot send the same business
event twice.

Record at least:

| Field | Purpose |
| - | - |
| Business key | Prevent two workers from creating separate attempts for the same business event. |
| `externalId` | Correlate YCloud data with your internal record and reconciliation reports. |
| YCloud `id` | Retrieve the message and match status events. |
| `wamid` | Correlate with WhatsApp after submission when this value is available. |
| Endpoint and attempt | Explain how the message was submitted and how many transport attempts occurred. |
| Current status and timestamps | Build the current operational view while retaining the event history. |

Use an opaque `externalId` that does not contain message content or personal
data. The API recommends a unique value, but `externalId` is a reference field.
It is not a server-side idempotency key and does not make repeated `POST`
requests safe.

## Connect the response to status webhooks

The following example uses the same identifiers through the send workflow.

### 1. Send the message

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/messages \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "from": "+16315551111",
    "to": "+16315552222",
    "type": "template",
    "externalId": "order-ready-10001",
    "filterUnsubscribed": true,
    "filterBlocked": true,
    "template": {
      "name": "orders_pickup_ready_v2",
      "language": {
        "code": "en_US",
        "policy": "deterministic"
      }
    }
  }'
```

### 2. Store the accepted response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "MESSAGE_ID",
  "wabaId": "WHATSAPP_BUSINESS_ACCOUNT_ID",
  "from": "+16315551111",
  "to": "+16315552222",
  "type": "template",
  "status": "accepted",
  "externalId": "order-ready-10001",
  "createTime": "2026-08-27T09:00:00.000Z"
}
```

Commit `MESSAGE_ID`, `accepted`, and the response time to the existing internal
record. Do not mark the business notification as delivered.

### 3. Apply later status events

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_MESSAGE_STATUS_1",
  "type": "whatsapp.message.updated",
  "apiVersion": "v2",
  "createTime": "2026-08-27T09:00:02.000Z",
  "whatsappMessage": {
    "id": "MESSAGE_ID",
    "wamid": "wamid.BgNODYxN...",
    "status": "sent",
    "externalId": "order-ready-10001",
    "sendTime": "2026-08-27T09:00:01.000Z"
  }
}
```

Match the event by `whatsappMessage.id`. Use `externalId` for business
reconciliation and `wamid` for provider-side investigation.

## Build a convergent status model

The common progression is `accepted` → `sent` → `delivered` → `read`.
`failed` can occur before or after a `sent` update. Webhooks can be duplicated,
delayed, or delivered out of order. A `read` update can also arrive without a
separate `delivered` event.

Process each event as follows:

1. Verify the webhook signature against the raw request body.
2. Durably store the event, using event `id` as the deduplication key.
3. Return a `2xx` response promptly, then process the event asynchronously.
4. Match `whatsappMessage.id` to the internal send record.
5. Store the event status and its available message timestamps. Keep the raw event metadata needed for an audit, but remove unnecessary message content.
6. Update the current business view without discarding conflicting or later evidence. Treat `read` as evidence that delivery occurred even when the separate `delivered` event is absent.
7. Retrieve `GET /whatsapp/messages/{id}` when events conflict, a terminal status is missing beyond your service objective, or the webhook pipeline was unavailable.

Do not implement the status model as a rule that only accepts a higher-ranked
status. Real delivery updates do not always arrive in that order. Retain an
event history and make reconciliation able to correct the current view.

## Retry without creating duplicate sends

Classify the failure before retrying.

| Failure | Recommended action |
| - | - |
| `400`, `404`, or `422` | Fix the request, resource, template, or business rule. Do not retry the unchanged request. |
| `401` or `403` | Fix authentication or account access. Do not retry unchanged credentials. |
| `429` | Reduce concurrency and retry after a delay. |
| `5xx` | Retry a temporary failure with exponential backoff, jitter, and a maximum attempt count. |
| Timeout or connection loss | Treat the outcome as ambiguous. Reconcile before creating another send whenever the request might have reached YCloud. |

A safe application policy can start with a small number of attempts, exponential
delays, full jitter, and a maximum elapsed time. These are application controls,
not API guarantees. Send exhausted attempts to a review queue instead of
retrying forever.

Before each retry:

* Lock or atomically claim the internal business key.
* Check whether the record already has a YCloud `id` or a status event.
* Do not use a new `externalId` to hide an earlier ambiguous attempt.
* Stop after the configured attempt or age limit.
* Require a deliberate operator action before replaying an ambiguous send.

## Choose templates and session messages

Use an approved template when you initiate a business message or send outside
the 24-hour customer service window. Select the template category from the
user's reason for receiving the message, and keep its name, language, and
variable contract in application configuration.

Use text, media, interactive, location, contact, or reaction messages only when
the customer service window is open and that content type is allowed. Determine
the window from the customer's most recent message. Do not infer an open window
from your last outbound message.

See [Manage WhatsApp templates](/en/api-reference/guides/whatsapp-platform/manage-whatsapp-templates)
for template versioning, approval gates, locales, and rollback.

## Handle media efficiently

* Validate the file's supported MIME type and size before upload. Do not retry
  an oversized or unsupported file unchanged.
* Upload with the business phone number that will send the message.
* Reuse the returned media ID for repeated sends of the same approved asset
  while it remains valid. Uploaded media persists for 30 days.
* Store the asset checksum, MIME type, media ID, sender, and expiration time so
  workers do not upload the same file for every recipient.
* Upload again after expiration or when the sender context changes.
* Use a public URL instead when the message schema requires a link, including
  media in interactive message headers.
* Stream large uploads from storage, set request timeouts, and delete temporary
  local files after use.

## Enforce consent and minimize data

Record the consent source, purpose, time, and permitted channel before sending.
Apply the latest valid opt-out across campaigns, transactional workflows where
policy requires it, retries, and manual replays.

For `POST /whatsapp/messages`, set `filterUnsubscribed: true` and
`filterBlocked: true` when the workflow must enforce YCloud suppression lists.
These fields default to `false`. They do not apply to `sendDirectly`, so a
direct-send workflow must check suppression before the API call.

Suppression filters are a final safety check, not a replacement for consent.
Store only the identifiers and delivery metadata needed for the stated purpose.
Exclude API keys, template variables, message bodies, and phone numbers from
general application logs. Apply retention and access controls to message and
webhook records.

## Control batch throughput

Place batch work in a bounded queue and send through a fixed worker pool. Track
concurrency separately by account and business phone number so one sender or
tenant cannot consume every worker.

Apply backpressure when any of these signals increase:

* `429` responses
* request latency and timeouts
* `5xx` responses
* queue age or retry backlog
* webhook lag and unresolved `accepted` messages

Reduce concurrency when YCloud or downstream delivery slows. Resume gradually
after recovery. Do not retry failed messages faster than the original send
rate.

Monitor at least request volume, accepted rate, error rate by HTTP status and
error code, delivery status rate, time from `accepted` to each later status,
queue depth, oldest queue age, retry count, webhook lag, deduplication count,
and reconciliation drift. Alert on sustained changes from your normal baseline,
not on a single failed message.

## Common anti-patterns

* Marking a message delivered when the API returns `accepted`.
* Treating `externalId` as a YCloud idempotency key.
* Retrying every non-`2xx` response or timeout with no attempt limit.
* Using `sendDirectly` for all traffic.
* Assuming webhooks are unique, ordered, or complete.
* Sending free-form messages outside the customer service window.
* Uploading the same media file for every recipient.
* Relying on suppression filters without recording consent.
* Logging API keys, full payloads, or unnecessary personal data.
* Starting a batch with unbounded concurrency and no backpressure.

## Go-live checklist

* [ ] The endpoint choice matches the workload and latency requirement.
* [ ] A database uniqueness rule protects the internal business key.
* [ ] `externalId`, YCloud `id`, and `wamid` have distinct documented roles.
* [ ] Initial responses remain non-final until status evidence arrives.
* [ ] Webhook signatures, event deduplication, fast acknowledgement, and replay are tested.
* [ ] A scheduled retrieval job reconciles delayed or missing events.
* [ ] Retryable and non-retryable failures have bounded handling paths.
* [ ] Template and session-window rules are enforced before sending.
* [ ] Media uploads are validated, reused, expired, and cleaned up safely.
* [ ] Consent, unsubscribe, block-list, retention, and logging controls are verified.
* [ ] Batch queues have concurrency limits, backpressure, dashboards, and alerts.
* [ ] Operators can pause sends and review ambiguous attempts without replaying them automatically.

<CardGroup cols={2}>
  <Card title="Send a WhatsApp message" icon="whatsapp" href="/en/api-reference/guides/whatsapp-platform/send-whatsapp-message">
    Review request types, fields, examples, and response data.
  </Card>

  <Card title="Configure webhooks" icon="webhook" href="/en/api-reference/guides/api-fundamentals/configure-webhooks">
    Verify signatures and process repeated event deliveries safely.
  </Card>

  <Card title="Upload WhatsApp media" icon="upload" href="/en/api-reference/guides/whatsapp-platform/upload-whatsapp-media">
    Upload supported media and reuse the returned media ID.
  </Card>

  <Card title="Handle API errors" icon="triangle-exclamation" href="/en/api-reference/guides/api-fundamentals/handle-errors">
    Parse error responses and apply bounded retries.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.