Send messages with the WhatsApp API: OTP codes, templates and text

Last updated: 11 October 2026 · 9 min read

Both templates and plain text go through one endpoint: POST /v1/accounts/{account_id}/whatsapp/messages. The message is accepted and queued right away, and the response comes back with 201.

Steps

  1. 1

    Find the template

    GET /v1/accounts/{account_id}/whatsapp/templates returns only APPROVED templates. channel_id is required, name is optional.

    Each template has name, language, category, parameter_format, components and example_parameters. example_parameters is a ready skeleton of the parameters the template expects: copy it and replace the values with your own data. Meta rejects a send with a missing variable.

    synced_at shows when the templates were last fetched from Meta. For the API to see a new template, click Sync Templates in the dashboard.

  2. 2

    Request fields

    • channel_id (required): the id of the WhatsApp Cloud API inbox
    • phone_number (required): the recipient's number, in international format
    • text: free text, sent only when the window is open
    • template: a template send, required if there is no text. template.name (the template's exact name) and template.language (az, en, tr, ru, en_US and so on) are required, and template.parameters are the template's variables
    • fallback_template: used together with text when the window is closed
    • conversation_status: resolved (default), open or pending
    • contact.name, contact.email, contact.identifier (the customer's id in your system): optional
    • source_reference: an idempotency key
    • status_callback: to receive the message's status at your own URL (url, after_seconds, on_change, secret), optional. Details are in the article on tracking a message's delivery status
  3. 3

    Send a verification code (OTP)

    Send it with an AUTHENTICATION category template. The request body:

    {
      "channel_id": 159,
      "phone_number": "+994 50 123 45 67",
      "template": {
        "name": "verification_code",
        "language": "az",
        "parameters": { "body": { "1": "483920" } }
      },
      "contact": { "name": "Kenan Rzayev", "identifier": "crm-10422" },
      "source_reference": "otp-10422-1754582400"
    }

    Keep the code's validity short; 5 minutes is recommended. One OTP message costs 0.036 AZN.

  4. 4

    Send a notification

    Send the text together with fallback_template:

    {
      "channel_id": 159,
      "phone_number": "+994 50 123 45 67",
      "text": "Your parcel AZ-10422 is with the courier.",
      "fallback_template": {
        "name": "kargo_status",
        "language": "az",
        "parameters": { "body": { "1": "AZ-10422", "2": "with the courier" } }
      }
    }

    If the window is open, the text goes out for free (sent_as: "session"); if it is closed, the template goes out (sent_as: "template").

  5. 5

    Template parameters

    Parameters are grouped by template part: header, body, footer, buttons.

    If parameter_format is POSITIONAL, the keys of body are numbers matching the order of {{1}}, {{2}}; if it is NAMED, they are the names you gave in Meta:

    "body": { "1": "Kenan", "2": "AZ-10422" }
    "body": { "customer_name": "Kenan", "order_id": "AZ-10422" }

    If there is only a body, a short form is also accepted: "parameters": { "1": "483920" }.

    • If the header is an image, video or document, send media_url (a public https address) and media_type: image (JPG, PNG), video (MP4), document (PDF and others, with media_name for the file name).
    • Buttons: url (the parameter is added to the end of the link) and copy_code (a copy-code button, at most 15 characters). Static link buttons and quick reply buttons need no parameter.
    • For an amount, the currency type (code, amount_1000, fallback_value); for a date, date_time (fallback_value). amount_1000 is the amount multiplied by 1000 as a whole number: 149000 for 149.00 AZN.
  6. 6

    Write the number correctly

    Write the number with the country code. All of these give the same result; spaces, hyphens, brackets and the “+” are removed automatically:

    994501234567
    +994 50 123 45 67
    (+994) 50-123-45-67

    0501234567 is wrong: there is no country code, so the message would go to the wrong number. The number must have 8 to 15 digits (E.164), otherwise 422 comes back.

  7. 7

    Read the response

    The response comes back as 201 Created: id (the message id), sent_as, status, external_error, whatsapp_message_id, conversation_id, conversation_status, contact_id, channel_id, content, template_name, source_reference, created_at, delivered_at, read_at, status_callback.

    status: "sent" does not mean delivered; it means Meta has accepted the message. delivered and read arrive a few seconds later, so do not tell the customer “delivered” right after sending.

  8. 8

    Prevent duplicate sends

    When the network drops or a queue runs twice, the same request can be repeated. If you pass source_reference, a repeated request with the same key within 24 hours does not create a new message and returns the first one: the customer does not get a second code, and Meta does not charge twice. Pick a key that is unique in your system, for example otp-{user}-{time}.

  9. 9

    Code samples

    Section 12 of the document has four samples:

    • Node.js: a cargo notification that switches to a template when the window is closed
    • PHP: a verification code
    • Python: check the window, then send
    • C# (.NET): a verification code

    In all of them the key is not in the code but in an environment variable (CLOMNI_TOKEN).

Document