WhatsApp

Send a WhatsApp message

Send an approved template anytime, or free-form text, media, and interactive messages inside the 24-hour service window.

With a default business account connected and an approved template, send WhatsApp through the unified messages API with channel: "whatsapp". What you can send depends on the recipient's 24-hour service window.

Send a template message

Templates can be sent anytime, inside or outside the service window, which makes them how you start a conversation. Reference the template by its handle or id and pass the values its schema declares in templateData. Use the SDK's raw HTTP client for the WhatsApp request. It uses your configured API key and accepts the endpoint's request body:

import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

const receipt = await samva.raw.client.post({
  url: "/v1/messages",
  body: {
    channel: "whatsapp",
    to: [{ phone: "+16175559876" }],
    whatsapp: {
      templateSlug: "order-update",
      templateData: { firstName: "Alice", orderNumber: "ORD-12345" },
    },
  },
  responseStyle: "data",
  throwOnError: true,
});

console.log(receipt);

To send one template to an audience or from a journey, see Broadcasts and journeys.

Send free-form text

Free-form text is only allowed inside the 24-hour window (opened when the customer last messaged you). Outside it, Samva rejects the send with a validation error telling you to use a template.

curl -X POST https://api.samva.dev/v1/messages \
  -H "X-API-Key: samva_sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "to": [{ "phone": "+16175559876" }],
    "whatsapp": { "text": "Thanks! Your order is on the way." }
  }'

Send media and interactive messages

Inside the window you can also send media, reply buttons, lists, call-to-action links, reactions, locations, and contacts. Each is one more key under whatsapp, and a send carries exactly one:

curl -X POST https://api.samva.dev/v1/messages \
  -H "X-API-Key: samva_sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "to": [{ "phone": "+16175559876" }],
    "whatsapp": {
      "media": { "type": "document", "url": "https://example.com/receipt.pdf", "caption": "Your receipt" }
    }
  }'

See Interactive and media messages for every content type, how to upload files, and how to receive the customer's replies.

Send errors

A send can be refused before it reaches Meta:

SituationError
Free-form content (anything but a template) outside the service windowValidation error: use a template.
The number is not registered yet (still pending verification)Sender not ready: finish verification, then sync the account.
Meta banned the numberForbidden: review the number in WhatsApp Manager, then sync it.
Meta disabled the WhatsApp business accountForbidden: sending resumes when Meta reinstates the account.
A template send to a new customer past the business portfolio's messaging limitRate limited, with retryAfterSeconds until the next slot opens.
The template has no Meta-approved registration on the accountConflict: submit the template and send once Meta approves it.
templateData does not match the template's schemaValidation error, naming the fields to fix.
A marketing template to a number with no recorded opt-inValidation error: record the opt-in first.

Every number in a business portfolio shares one messaging limit. See Business account for what each state means.

Opt-in and opt-out

Samva records each number's WhatsApp consent on its contact. A recipient who replies STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, or QUIT (the whole message, in any case) is opted out of WhatsApp, and START or UNSTOP opts them back in. Samva records the change with the source whatsapp_keyword. When a customer stops or resumes marketing messages in WhatsApp itself, Meta reports it on the user_preferences webhook field and Samva records it with the source whatsapp_preferences. Each change publishes contact.consent_changed.

An opted-out number is never sent to: the message fails with errorCode SUPPRESSED and a message.failed webhook. A template whose category is marketing also needs the number's recorded opt-in. Without it, the request is refused with a 422 validation error, and a message that waited in the queue while the opt-in was withdrawn fails with errorCode CONSENT_REQUIRED. Record the opt-in with PUT /v1/contacts/{id}/identities/{identityId}/consent/whatsapp. Utility templates need no recorded opt-in.

Recipients

to is an array; each entry is a WhatsApp number or a stored contact:

FormExample
WhatsApp number{ "phone": "+16175559876" }
Existing contact reference{ "contactId": "contact_k9mvt4znw8rh7q2x" }

Check the service window

Before a free-form send, you can check whether the window is open:

curl "https://api.samva.dev/v1/whatsapp/service-window/status?customerPhone=+16175559876&businessPhone=+16175550123" \
  -H "X-API-Key: samva_sk_live_your_api_key"
{
  "serviceWindowId": "swin_a1b2c3",
  "isServiceWindowActive": true,
  "serviceWindowExpiresAt": "2026-06-23T10:30:00.000Z",
  "isFreeEntryPointActive": false,
  "freeEntryPointExpiresAt": null
}

Delivery status

Track a send by its id: WhatsApp reports sent, delivered, read, and failed:

import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

const message = await samva.messages.get({ id: "msg_7q2xk9mvt4znw8rh" });
console.log("Status:", message.status);

Related documentation

On this page