Send WhatsApp

Send WhatsApp Business messages from your own software: approved templates to start a conversation, and text, images and documents to reply.

POST one message per call to https://api.everymessage.com/v1/whatsapp/messages. To contact someone first, use an approved template. Once they message you, you can send free-form text and media for 24 hours.

Before you start: WhatsApp must be switched on for your account and your business number connected. Templates are created in the portal and approved by WhatsApp. Read about WhatsApp or contact us to set it up.

Method

POSThttps://api.everymessage.com/v1/whatsapp/messages

Send Content-Type: application/json and authenticate with HTTP Basic (details).

The WhatsApp API uses HTTP status codes. Unlike /v1/submit, it does not use the response envelope: 202 means accepted, and errors return a 4xx status with {"error": "…"}.

Request

Template

{
  "from": "447700900000",
  "to": "447700900123",
  "type": "template",
  "template": {
    "name": "order_update",
    "language": "en_GB",
    "body": [
      "Sam",
      "A1042"
    ]
  },
  "reference": "order-A1042"
}

Template with image and button

{
  "from": "447700900000",
  "to": "447700900123",
  "type": "template",
  "template": {
    "name": "appointment_card",
    "language": "en_GB",
    "header": {
      "type": "image",
      "link": "https://example.com/img/clinic.png"
    },
    "body": [
      "Sam",
      "Tuesday 14 October",
      "10:30"
    ],
    "buttons": [
      {
        "index": 0,
        "type": "url",
        "value": "A1042"
      }
    ]
  }
}

Text reply

{
  "from": "447700900000",
  "to": "447700900123",
  "type": "text",
  "text": {
    "body": "Thanks Sam, we've moved your appointment to 11:15."
  }
}

Image

{
  "from": "447700900000",
  "to": "447700900123",
  "type": "image",
  "image": {
    "link": "https://example.com/img/map.png",
    "caption": "How to find us"
  }
}

Document

{
  "from": "447700900000",
  "to": "447700900123",
  "type": "document",
  "document": {
    "link": "https://example.com/files/invoice-A1042.pdf",
    "filename": "invoice-A1042.pdf",
    "caption": "Your invoice"
  }
}

Request body

FieldTypeDescription
from requiredstringYour WhatsApp business number, for example 447700900000.
to requiredstringThe recipient’s WhatsApp number in international format, for example 447700900123. One recipient per call.
type requiredstringtemplate, text, image, document, video or audio. Then supply the matching object below.
templateobjectFor type: template. See Template.
textobjectFor type: text: {"body": "…"}. Up to 4,096 characters.
image, document, video, audioobjectFor media types. See Media.
referencestringYour own reference. It comes back on the delivery receipt.
replyUrlstringWhere to send delivery receipts by webhook. See receipts and replies.

Template

FieldTypeDescription
name requiredstringThe template name, exactly as approved.
language requiredstringThe template language, for example en_GB.
bodyarrayValues for the template’s placeholders, in order: the first fills {{1}}, the second {{2}}, and so on. Must match the number in the template.
headerobjectOnly if the template header has a variable. Text: {"type": "text", "text": "…"}. Media: {"type": "image", "link": "https://…"} (or document / video).
buttonsarrayOnly for buttons that take a value: {"index": 0, "value": "…"}, where index is the button’s position starting at 0. For a website button, the value is added to the end of its URL; for a copy-code button it is the code.

We check the values against the approved template before sending, so a wrong number of values is rejected with 400 instead of failing later.

Media

FieldTypeDescription
linkstringA public HTTPS link to the file. Either link or id is required.
idstringA WhatsApp media id, if you have already uploaded the file.
captionstringText shown with an image, video or document.
filenamestringThe file name shown for a document.

24-hour window

WhatsApp only allows free-form messages (text and media) within 24 hours of the customer’s last message to you. Outside that window, start the conversation with an approved template. If you send free-form outside the window, the API returns 409 and nothing is sent or charged. A common pattern is to try a text reply and, on 409, send a template instead.

Each message is charged by its WhatsApp category: marketing, utility or authentication for templates, and service for free-form replies. The category in the response shows which. See WhatsApp message types and pricing for current costs.

Response

HTTP 202 Accepted:

{
  "status": "accepted",
  "channel": "WHATSAPP",
  "to": "447700900123",
  "type": "template",
  "category": "UTILITY",
  "messageId": 48240,
  "conversationId": "K7Q2M4XW9PLD3T6B",
  "charged": 0.03,
  "transactionId": 4830
}
FieldTypeDescription
statusstringaccepted: queued for delivery.
tostringThe recipient.
typestringThe message type sent.
categorystringThe template’s category (MARKETING, UTILITY or AUTHENTICATION). Left out for free-form messages, which are charged as service messages.
messageIdintegerOur id for the message. Delivery receipts refer to it.
conversationIdstringIdentifies the conversation with this person.
chargednumberCost of the message.
transactionIdintegerOur id for the request.

Errors

HTTP statusMeaning
400Missing or invalid fields, or the template values don’t match the approved template.
401Wrong api_key or secret_key.
402Not enough credit on a prepay account.
403WhatsApp is not enabled for your account, or from is not one of your WhatsApp numbers.
404No template with that name and language.
409The template is not approved yet, or a free-form message was sent outside the 24-hour window.
429Your daily sending limit has been reached.
500Something went wrong on our side. Safe to retry after a short wait.

List templates

GEThttps://api.everymessage.com/v1/whatsapp/templates

Lists your templates with their approval status and how many values each one needs. Filter with ?status=APPROVED or ?category=UTILITY.

{
  "templates": [
    {
      "id": 12,
      "companyId": 2,
      "wabaId": "104512345678901",
      "name": "order_update",
      "language": "en_GB",
      "category": "UTILITY",
      "status": "APPROVED",
      "metaTemplateId": "998877665544332",
      "bodyParamCount": 2,
      "headerFormat": null,
      "headerParamCount": 0,
      "buttonCount": 0,
      "rejectedReason": null
    }
  ]
}

bodyParamCount is how many values to send in template.body; headerFormat and buttonCount tell you whether a header or button values are needed. Templates can also be created by API with POST /v1/whatsapp/templates, though most customers use the portal’s template designer.

Receipts and replies

Delivery receipts arrive in the same format as SMS receipts: by webhook to replyUrl (or your account’s default receipt URL), or by collecting them. A message that is delivered or read is reported as DELIVERED; one WhatsApp could not deliver is FAILED. See delivery receipts.

Messages from your customers are forwarded to the webhook URL (or workflow) set up for your WhatsApp number. Each reply also opens a new 24-hour window for free-form messages.

Code examples

Send a template message

Both examples read the keys from EM_API_KEY and EM_SECRET_KEY. C# needs .NET 8 or later; Python needs 3.10 or later and pip install httpx.

C#

// Send a WhatsApp template message with the everymessage v1 API (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;

using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));

var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

// One recipient per call. "from" is your WhatsApp business number.
var request = new WhatsAppMessage(
    From: "447700900000",
    To: "447700900123",
    Type: "template",
    Template: new Template(
        Name: "order_update",
        Language: "en_GB",
        Body: ["Sam", "A1042"]),          // fills {{1}} and {{2}} in the approved template
    Reference: "order-A1042");

using var response = await http.PostAsJsonAsync("v1/whatsapp/messages", request, json);

// The WhatsApp API uses HTTP status codes: 202 means accepted.
// 409 means the template isn't approved, or a free-form message is outside the 24-hour window.
if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException(
        $"WhatsApp send failed ({(int)response.StatusCode}): {await response.Content.ReadAsStringAsync()}");

var result = await response.Content.ReadFromJsonAsync<SendResult>(json)
             ?? throw new InvalidOperationException("Empty response from everymessage");

Console.WriteLine($"{result.To}: {result.Status} (message {result.MessageId}, {result.Category}, charged {result.Charged})");

record WhatsAppMessage(string From, string To, string Type, Template? Template = null,
    TextBody? Text = null, string? Reference = null);
record Template(string Name, string Language, IReadOnlyList<string>? Body = null);
record TextBody(string Body);

record SendResult(string Status, string To, string Type, string? Category, long? MessageId, decimal Charged);

Python

"""Send a WhatsApp template message with the everymessage v1 API (Python 3.10+, pip install httpx)."""
import os

import httpx

# One recipient per call. "from" is your WhatsApp business number.
payload = {
    "from": "447700900000",
    "to": "447700900123",
    "type": "template",
    "template": {
        "name": "order_update",
        "language": "en_GB",
        "body": ["Sam", "A1042"],  # fills {{1}} and {{2}} in the approved template
    },
    "reference": "order-A1042",
}

with httpx.Client(
    base_url="https://api.everymessage.com",
    auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
    timeout=30,
) as client:
    response = client.post("/v1/whatsapp/messages", json=payload)

# The WhatsApp API uses HTTP status codes: 202 means accepted.
# 409 means the template isn't approved, or a free-form message is outside the 24-hour window.
if response.is_error:
    raise RuntimeError(f"WhatsApp send failed ({response.status_code}): {response.text}")

r = response.json()
print(f"{r['to']}: {r['status']} (message {r.get('messageId')}, {r.get('category')}, charged {r['charged']})")

List approved templates

C#

// List your approved WhatsApp templates with the everymessage v1 API (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;

var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;

using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));

using var response = await http.GetAsync("v1/whatsapp/templates?status=APPROVED");
if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException(
        $"List failed ({(int)response.StatusCode}): {await response.Content.ReadAsStringAsync()}");

var result = await response.Content.ReadFromJsonAsync<TemplateList>()
             ?? throw new InvalidOperationException("Empty response from everymessage");

foreach (var t in result.Templates)
    Console.WriteLine($"{t.Name} ({t.Language}, {t.Category}): {t.BodyParamCount} body parameter(s)" +
        (t.HeaderFormat is null ? "" : $", {t.HeaderFormat} header"));

record TemplateList(IReadOnlyList<TemplateInfo> Templates);
record TemplateInfo(string Name, string Language, string Category, string Status,
    int BodyParamCount, string? HeaderFormat, int ButtonCount);

Python

"""List your approved WhatsApp templates with the everymessage v1 API (Python 3.10+, pip install httpx)."""
import os

import httpx

with httpx.Client(
    base_url="https://api.everymessage.com",
    auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
    timeout=30,
) as client:
    response = client.get("/v1/whatsapp/templates", params={"status": "APPROVED"})

if response.is_error:
    raise RuntimeError(f"List failed ({response.status_code}): {response.text}")

for t in response.json()["templates"]:
    header = f", {t['headerFormat']} header" if t.get("headerFormat") else ""
    print(f"{t['name']} ({t['language']}, {t['category']}): "
          f"{t['bodyParamCount']} body parameter(s){header}")