KanalKanal API
Messaging API

Send a message

Send a free-form WhatsApp message inside an open 24h window.

POST /api/v1/messages/send

Sends a free-form WhatsApp message. Only works inside an open 24-hour window — the contact must have messaged you in the last 24h. For outbound-first messaging use Send a template.

Body

The format is Meta-like: a type plus an object named after that type.

FieldTypeRequiredNotes
tostringrequiredRecipient phone, country code included. Non-digits are stripped; validated as a possible number.
typestringrequiredOne of text, image, video, audio, document.
text.bodystringrequired if type=textMessage text, non-empty.
<type>.linkstring (URL)required if typetextMedia URL for image/video/audio/document.
<type>.captionstringoptionalUsed only for image and video; ignored for audio/document.

Optional header

HeaderNotes
Idempotency-KeyIf a previous request used the same value, the message is not re-sent — returns 201 with a note.

Request

curl -X POST https://api.getkanal.com/api/v1/messages/send \
  -H "Authorization: Bearer 8fK2pX9mWq4Ld7Vb3Nc6Ts1Z" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f9c2b1e-2b8a-4e5f-9c1d-aa0011223344" \
  -d '{
    "to": "33612345678",
    "type": "text",
    "text": { "body": "Hello from the API!" }
  }'

Media example:

curl -X POST https://api.getkanal.com/api/v1/messages/send \
  -H "Authorization: Bearer 8fK2pX9mWq4Ld7Vb3Nc6Ts1Z" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "33612345678",
    "type": "image",
    "image": {
      "link": "https://example.com/photo.jpg",
      "caption": "Optional caption"
    }
  }'

Response 200 OK

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

When an Idempotency-Key matches a prior request, you get 201:

{ "message": "Message already sent with this idempotency key: <key>" }

Errors

All errors use the flat { "error": "..." } shape (this endpoint has no 422 validation envelope).

StatusBodyCause
401{ "error": "API key is missing" } / { "error": "Invalid API key" }Auth.
403{ "error": "Team has no active subscription" }No active subscription.
400{ "error": "type is required and must be one of: text, image, video, audio, document" }Bad/missing type.
400{ "error": "text.body is required for type text" }Missing text body.
400{ "error": "<type>.link is required for type <type>" }Missing media link.
400{ "error": "to is required" } / { "error": "Phone number is not valid" }Bad recipient.
400{ "error": "Contact has opted out from receiving messages", "contact_id": …, "phone_number": "…" }Recipient opted out.
400{ "error": "Cannot send free-form message: no active conversation window…", "contact_id": …, "phone_number": "…" }No open 24h window — use a template.
400{ "error": "Error sending message", "details": "…" }WhatsApp send failed.

On this page