POST /v1/actions/send-template-message

Send a Meta-approved WhatsApp template message to a contact. Works any time — including outside the 24h customer service window.

Sends a Meta-approved template message. Templates are the only way to message a contact outside the 24-hour customer service window — and the only way to start a conversation with someone you've never messaged.

The contact is upserted by (tenant, phone) automatically — you don't need to call create-contact first.

POST /api/v1/actions/send-template-message

Request body

Field Type Required Notes
phone string yes International format with + (e.g. +14155550123). Qyvo normalizes minor variations.
template_id UUID yes The Qyvo template id (not the Meta name). List templates to discover ids.
language string no e.g. en, fr. If omitted (or not approved), Qyvo falls back to any approved translation.
variables object conditional Required when the template body has {{1}}, {{2}} placeholders. Keys are the placeholder names, values are strings.
curl -X POST https://www.qyvo.io/api/v1/actions/send-template-message \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+14155550123",
    "template_id": "01J0AB...",
    "language": "en",
    "variables": {
      "1": "Romain",
      "2": "ORD-1042"
    }
  }'
const res = await fetch('https://www.qyvo.io/api/v1/actions/send-template-message', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.QYVO_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    phone: '+14155550123',
    template_id: '01J0AB...',
    language: 'en',
    variables: { 1: 'Romain', 2: 'ORD-1042' },
  }),
});
Http::withToken(env('QYVO_TOKEN'))
    ->post('https://www.qyvo.io/api/v1/actions/send-template-message', [
        'phone' => '+14155550123',
        'template_id' => '01J0AB...',
        'language' => 'en',
        'variables' => ['1' => 'Romain', '2' => 'ORD-1042'],
    ])
    ->json();
import os, httpx
httpx.post(
    'https://www.qyvo.io/api/v1/actions/send-template-message',
    headers={'Authorization': f"Bearer {os.environ['QYVO_TOKEN']}"},
    json={
        'phone': '+14155550123',
        'template_id': '01J0AB...',
        'language': 'en',
        'variables': {'1': 'Romain', '2': 'ORD-1042'},
    },
).raise_for_status()

Response — 200 OK

{
  "id": "01J1Z...",
  "status": "sent",
  "whatsapp_message_id": "wamid.HBgL...",
  "contact_id": "01J1Y..."
}
Field Notes
id Qyvo message id — appears in the Inbox; receives subsequent delivered/read status updates
status sent — Meta accepted the request. Async statuses (delivered, read, failed) flow through webhooks.
whatsapp_message_id Meta's wamid.* — useful for cross-referencing with Meta's logs
contact_id The contact (created if it didn't exist)

Errors

Status Body Cause
404 Template not found The template_id doesn't match any template in your tenant
422 Template requires variables that were not provided. Body has {{N}} you didn't pass. Response includes expected_placeholders and missing.
422 Template has no approved translation to send. Template is PENDING/REJECTED on Meta. Response includes available_languages.
422 No WhatsApp account configured Your tenant has no WabaAccount — onboard a number first
422 Meta error message bubbled up See Errors → Meta codes

Variables and placeholders

The template body might be:

Hi {{name}}, your order {{order}} is shipping today. Track it: {{tracking_url}}

Placeholders are extracted by Qyvo from the body. The variables keys must match — both numeric (1, 2, …) and named placeholders are supported, depending on how you authored the template on Meta.

If you pass too few keys, you'll get back:

{
  "error": "Template requires variables that were not provided.",
  "expected_placeholders": ["name", "order", "tracking_url"],
  "missing": ["tracking_url"]
}

Values must be strings — coerce numbers/dates client-side.