---
title: "Messaging API: send messages and trigger automations"
description: "Send free-form messages and templates, list your templates, trigger an automation, rename a contact and read your account information through the API."
url: https://help.getkanal.com/en/articles/81-messaging-api-send-messages-and-trigger-automations
collection: "API"
updated: 2026-10-21
language: en
alternates: [fr-fr]
---

# Messaging API: send messages and trigger automations

Send free-form messages and templates, list your templates, trigger an automation, rename a contact and read your account information through the API.

The Messaging API groups the endpoints that send WhatsApp messages, trigger automations, rename your contacts and read your account information. It requires an **active subscription**: without one, every call returns `403 Team has no active subscription`.

These endpoints do not take a `store_id`: your API key is attached to a WhatsApp number, which is the sending number. See [API authentication and keys](https://help.getkanal.com/en/articles/14-api-authentication-and-keys.md).

## Free-form message or template?

WhatsApp only allows free-form messages within an **open 24-hour service window**, that is, when the contact has written to you in the last 24 hours. To contact someone first, you must use an approved template.

## Sending a free-form message

```text
POST /api/v1/messages/send
```

The format follows Meta's logic: a `type` field, then an object named after that type.

-   `to` (required): the recipient's number, including the country code.
    
-   `type` (required): `text`, `image`, `video`, `audio` or `document`.
    
-   `text.body`: required for the text type.
    
-   `<type>.link`: required for all other types (media URL).
    
-   `<type>.caption` (optional): caption, accepted for images, videos and documents.
    
-   `document.filename` (optional): file name shown to the recipient.
    

```bash
curl -X POST https://api.getkanal.com/api/v1/messages/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "33612345678",
    "type": "text",
    "text": { "body": "Hello from the API!" }
  }'
```

Success response (`200`):

```json
{
  "success": true,
  "message": "Message sent successfully",
  "message_id": 123456,
  "contact_id": 7890,
  "phone_number": "33612345678"
}
```

For an image:

```json
{
  "to": "33612345678",
  "type": "image",
  "image": {
    "link": "https://example.com/photo.jpg",
    "caption": "Optional caption"
  }
}
```

For a document:

```json
{
  "to": "33612345678",
  "type": "document",
  "document": {
    "link": "https://example.com/invoice.pdf",
    "caption": "Your invoice",
    "filename": "invoice-10456.pdf"
  }
}
```

The most common error on this endpoint is `Cannot send free-form message: no active conversation window`. It means the 24-hour window is closed: use a template. The other cases are an invalid number, an opted-out contact, or a badly filled message type.

## Sending a template

```text
POST /api/v1/templates
```

This is the only way to contact a customer first. The header media (image, video or document) always comes from the template: it cannot be passed in the request. Parameters and the variables format are detailed in [Send a WhatsApp template via the API](https://help.getkanal.com/en/articles/15-send-a-whatsapp-template-via-the-api.md).

## Listing your approved templates

```text
GET /api/v1/get_templates
```

Returns an array of your templates with the `APPROVED` status for the number attached to the key, including those created in an automation or a campaign. No parameters: only the `Authorization` header is needed.

```json
[
  {
    "id": 1234,
    "phone_number_id": 56,
    "name": "order_confirmation",
    "wa_status": "APPROVED",
    "structure": "{ ...JSON: header/body/buttons... }",
    "created_at": "2026-01-10T12:00:00.000000Z"
  }
]
```

The `id` field is the `template_id` to use when sending. The `structure` field is a JSON string describing the header, body and buttons: the variable names it contains are exactly the keys to pass in the `variables` object when sending.

## Triggering an automation

```text
POST /api/v1/automations/{webhook_id}
```

Starts an automation whose trigger is an **Incoming Webhook**. The `webhook_id` is the UUID of this node. In the automation editor, the **Incoming Webhook** node shows the full **Destination URL** and a button to copy the call as cURL. The automation must be **active** and belong to the key's WhatsApp number.

Fields accepted at the root of the JSON:

-   `phone_number` (required): the contact's number. If no contact exists for this number, it is created.
    
-   `variables` (optional): a key/value object injected into the automation's session variables.
    
-   `name`, `first_name`, `last_name` (optional): the contact's identity.
    
-   `email` (optional): added only if the contact does not have an email yet.
    
-   `shopify_customer_id` (optional): must be numeric, links the contact to a Shopify customer.
    

```bash
curl -X POST https://api.getkanal.com/api/v1/automations/3f9c1a2b-4d5e-6789-abcd-ef0123456789 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+33612345678",
    "first_name": "Marie",
    "email": "marie@example.com",
    "variables": { "discount_code": "WELCOME10" }
  }'
```

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p><strong>Code</strong></p></th><th colspan="1" rowspan="1"><p><strong>Message</strong></p></th><th colspan="1" rowspan="1"><p><strong>Meaning</strong></p></th></tr><tr><td colspan="1" rowspan="1"><p><code>200</code></p></td><td colspan="1" rowspan="1"><p><code>Incoming webhook triggered</code></p></td><td colspan="1" rowspan="1"><p>The flow has started.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>200</code></p></td><td colspan="1" rowspan="1"><p><code>Contact blocked by re-entry rule</code></p></td><td colspan="1" rowspan="1"><p>The automation's re-entry rule blocks this contact: no flow starts.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>400</code></p></td><td colspan="1" rowspan="1"><p><code>Automation is not active</code></p></td><td colspan="1" rowspan="1"><p>The automation is turned off: activate it.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>400</code></p></td><td colspan="1" rowspan="1"><p><code>phone_number is required</code> or <code>Invalid phone_number format</code></p></td><td colspan="1" rowspan="1"><p>Missing or invalid number.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>400</code></p></td><td colspan="1" rowspan="1"><p><code>shopify_customer_id must be numeric</code></p></td><td colspan="1" rowspan="1"><p>The Shopify customer ID is not numeric.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>401</code></p></td><td colspan="1" rowspan="1"><p><code>Invalid API key</code></p></td><td colspan="1" rowspan="1"><p>The automation belongs to a different WhatsApp number than the key's.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>404</code></p></td><td colspan="1" rowspan="1"><p><code>Incoming webhook not found</code></p></td><td colspan="1" rowspan="1"><p>The <code>webhook_id</code> is unknown.</p></td></tr></tbody></table>

> **Info:** **Note:** a `200` response does not guarantee that the flow started: check the `message` field. This endpoint uses a `{ "message": ... }` format, not `{ "error": ... }`.

Older URLs of the form `https://api.getkanal.com/v1/automations/{webhook_id}` (without `/api`) are still accepted for existing integrations. Use `/api/v1/` for any new integration.

## Renaming a contact

```text
PATCH /api/v1/contacts/{phone}
```

Available since October 8, 2026. This endpoint changes the name of an existing contact of your number. It never creates a contact: an unknown number returns `404 Contact not found`.

-   `{phone}`: the contact's number, including the country code, with or without `+` (for example `33612345678`).
    
-   `name`: the name shown in the Inbox. It does not change `first_name` or `last_name`.
    
-   `first_name`: the first name, used by the personalization variables of templates and automations. If you send `first_name` without `name`, the name shown in the Inbox is also changed.
    
-   `last_name`: the last name.
    

Send at least one of these three fields, otherwise the API returns a `422` error. Only the fields you send are changed.

```bash
curl -X PATCH https://api.getkanal.com/api/v1/contacts/33612345678 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "140 - John Smith" }'
```

> **Info:** Your integrations can overwrite these names after your call. With Shopify, each new order from the contact resets the displayed name, first name and last name to those of the Shopify customer. With the Commerce API, each data push updates the first and last name, but keeps the name shown in the Inbox.

## Account information

```text
POST /api/v1/me
```

Returns the WhatsApp Business number attached to the key. No request body. It is the fastest call to check that a key works and confirm which number it sends from.

```json
{
  "display_phone_number": "+33 6 12 34 56 78",
  "verified_name": "My Shop"
}
```

## Avoiding duplicate sends

The `/messages/send` and `/templates` endpoints accept an optional `Idempotency-Key` header. Replaying a request with the same key does not send the message again: the API returns a `201` with the message `Message already sent with this idempotency key`. Use a unique value, for example one UUID per message: a key already known to Kanal is treated as a duplicate.

> **Warning:** **After an error, use a new key.** If a first send failed (4xx or 5xx error), a new attempt with the same `Idempotency-Key` answers "already sent" without sending anything. Generate a new `Idempotency-Key` for each new attempt after an error.

## Rate limits

Only `POST /api/v1/templates` is limited to **60 requests per minute** per API key. Beyond that, the API returns a `429` error with `retry_after: 60`. The other Messaging API endpoints are only subject to the global API limit: 3,000 requests per minute per IP address.
