Send your application's own Meta-approved WhatsApp templates to a workspace's customers, through Connect's WhatsApp sender - and check whether a template is live before offering it.
Send a workspace's customer one of your application's own WhatsApp messages - a quote, a booking, a document ready - without a WhatsApp Business account of your own.
Templates, never free text
Everything you send is a template Meta has approved, named for your application. Outside a customer's 24-hour window WhatsApp permits nothing else, and a free-form conversation with a workspace's customers on the workspace's own number belongs to the CosmoOps Messaging application, not to this API.
Templates are written and submitted to Meta by the platform team, in Console, under your application. Connect names each one app_<yourApplicationKey>_<slug> and resolves it from your key - so a request names only the slug (quote-issued), and can never reach another application's message or one of Connect's own.
Approval takes Meta anywhere from minutes to days, and a template can be paused later for quality. So ask before you offer: GET /whatsapp/templates/{slug} answers live, and the variables a send must carry. A screen that offers WhatsApp only when live is true never shows a button that cannot work.
Consent is the customer's, and a STOP is final
Connect messages a number only with an opt-in on file, keyed by the number across the whole platform. Your application collects opt-ins Connect never sees - a lead form, a call, a signed quote - so a send may carry consent.collectedVia, saying where the customer agreed. Connect records it against the number, as yours, only when nothing is on file.
A number that has withdrawn - replied STOP, or turned WhatsApp off in their profile - stays withdrawn. Attesting consent cannot re-grant it; the send is refused with opted_out, and the honest next step is a different channel.
Treat it as a second channel
A send can be refused for reasons nobody did anything wrong to cause - a template under review, an opted-out number. Send the same thing by email as the channel of record, and WhatsApp beside it.
Endpoints
POST /api/v1/application/whatsapp/messages
Send one of your WhatsApp templates to a customer
Scope whatsapp:send · Rate limit 30 requests / min per key, 10 requests / hour per key+recipient
Sends a Meta-approved template your application owns to a customer of a subscribing workspace, through Connect's WhatsApp sender. Server-to-server only - there is no browser path.
Implementation
1. Prerequisite - an API key scoped to whatsapp:send, and a template under your application in Console that Meta has approved. Ask GET /whatsapp/templates/{slug} first: offer WhatsApp only when it answers live: true.
2. Send - name the template by its slug, never its Meta name, and pass one value per variable:
curl -X POST https://connect.cosmoops.com/api/v1/application/whatsapp/messages \
-H "Authorization: Bearer YOUR_API_KEY_PLAINTEXT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quote-Q-0042-whatsapp" \
-d '{"workspaceId":"WORKSPACE_ID","template":"quote-issued","to":"+919876543210","values":{"number":"Q-0042","link":"..."},"consent":{"collectedVia":"Quote request form"}}'3. Consent. Connect sends only to a number with an opt-in on file. consent.collectedVia records the one your application collected - only when nothing is on file. A number that withdrew (replied STOP, or turned WhatsApp off) stays withdrawn: the send answers opted_out whatever you attest.
Errors
validation_error (400 - bad body, a non-E.164 to, or a template variable without a value), forbidden (403 - key missing whatsapp:send, or your application is not installed in that workspace), consent_required (403 - no opt-in on file and none attested), opted_out (403 - the number withdrew, for everything or for this kind of message), not_found (404 - your application has no such template in that language), template_not_approved (409 - not approved by Meta yet, or paused), idempotency_key_reused/request_in_progress (409), rate_limited (429 - per key, and per key and recipient), delivery_failed (502 - retry).
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes | |
template | string | yes | |
language | string | no | |
to | string | yes | |
values | object | no | |
consent | object | no |
Responses
| Status | Meaning |
|---|---|
201 | Sent |
400 | validation_error - bad body, phone number, or a missing template value |
403 | forbidden / consent_required / opted_out |
404 | not_found - no such template for your application |
409 | template_not_approved / idempotency_key_reused / request_in_progress |
429 | rate_limited |
502 | delivery_failed - the WhatsApp provider refused the message |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
status | string | yes | |
messageId | string | yes |
GET /api/v1/application/whatsapp/templates/{slug}
Check whether a WhatsApp template is live
Scope whatsapp:send · Rate limit 30 requests / min per key
Whether one of your application's templates would send right now, and the values a send must carry.
live is the one field to branch on: Meta has approved the template and is currently serving it. A template being re-reviewed after an edit, or paused for quality, answers live: false even if it was approved before. variables lists the names from the approved content, in order - the keys values must carry on a send.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
slug | path | yes | The template's slug within your application, e.g. quote-issued. |
language | query | no | Meta language code. Defaults to en. |
Responses
| Status | Meaning |
|---|---|
200 | The template's live state |
403 | forbidden - key unknown or missing the whatsapp:send scope |
404 | not_found - your application has no such template in that language |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
slug | string | yes | |
language | string | yes | |
name | string | yes | |
status | DRAFT | PENDING | APPROVED | REJECTED | PAUSED | DISABLED | yes | |
live | boolean | yes | |
variables | array | yes |