Skip to content
KonekSip API

API Reference

Every endpoint, webhook payload and error code of the KonekSip REST API.

Base URL

https://koneksip.com/api

A machine-readable OpenAPI 3.1 description of this API is at https://koneksip.com/api/openapi.json. It needs no API key, and you can import it into Postman or Insomnia.

Authentication

All API requests require authentication using an API key, sent in the Authorization header.

Header Authentication

Send your API key as a Bearer token on every request.

Authorization: Bearer YOUR_API_KEY

Note: Keep your API key secure and never share it publicly.

Conventions

  • Request and response bodies are JSON. Send Content-Type: application/json on requests with a body.
  • Ids in the path ({message_id}, {receiver}, ...) must be URL-encoded.
  • message_id is the id of the message on its channel. The value in a send response, in webhooks and in the path of Mark as Read, React, Edit and Delete is the same. Its format depends on the channel: 3EB0… on Unofficial (QR) channels, wamid.… on Official (Cloud API) channels and the Message-ID header (<…@…>) on email.
  • Success is the HTTP status: 200 means the request succeeded. There is no status field in the body.
  • A success body always holds a message and, when there is a result, a data field. Only responses use data, request fields are flat.
  • Each plan has a monthly message limit. Once it is reached, endpoints that send return 429 quota_exceeded until the next month or an upgrade.
  • On a WhatsApp number managed by KonekSip, a template, and a reply that WhatsApp charges (after the first 1,000 a month), is paid from the prepaid wallet. Without enough balance the send returns 402 insufficient_balance.
  • Requests that send a message or an email are limited to 60 per minute for each API key, and all requests together to 120 per minute. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets).
  • A WhatsApp user who adopted a username may reach you without a phone number. Meta then identifies them with a business-scoped user ID (BSUID, for example ID.13491208655302741918). Such a conversation uses the BSUID as its receiver: you see it in webhooks and in the read endpoints, and you reply by sending it as receiver. This works on Official (Cloud API) channels. Phone-number receivers keep working.
  • Errors are described in Errors.
  • Some endpoints only work on Unofficial (QR) channels and say so, Official channels get an unsupported_channel error there.

Idempotency

Send an Idempotency-Key header (any unique string, up to 255 characters) with POST /api/messages and POST /api/emails. If you retry after a timeout with the same key and body, the first successful response is returned again, with the header Idempotent-Replayed: true, and nothing is sent twice. Keys are remembered for 24 hours. Reusing a key with a different body returns idempotency_key_reused, a retry while the first request is still running returns idempotency_in_progress. Failed requests do not keep the key.

Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7

Send Text

Send a text message to a WhatsApp number. Every message, whatever its kind, is sent with POST /api/messages, the type field (text, media, buttons or template) picks the kind and the other fields of the body depend on it.

POST /api/messages

Request Body

{
  "type": "text",
  "receiver": "string (required) - Phone number with country code (e.g., 628123456789)",
  "message": "string (required) - Message content",
  "reply_to": "string (optional) - message_id of an earlier message to quote",
  "typing": "boolean (optional, default false) - WhatsApp (QR) only. true shows \"typing...\" before sending, so the reply takes up to 3 seconds longer"
}

Example Request

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "text",
  "receiver": "628123456789",
  "message": "Hello! This is a test message."
}

Success Response (200 OK)

{
  "message": "Message sent.",
  "data": { "message_id": "3EB0C767D0B1A2F4E5A9B3" }
}

Error Response

{
  "code": "invalid_receiver",
  "message": "Invalid WhatsApp number."
}

Send Media

Send images, videos, documents, or audio files.

POST /api/messages

Request Body

{
  "type": "media",
  "receiver": "string (required) - Phone number with country code (e.g., 628123456789)",
  "url": "string (required) - Media URL",
  "caption": "string (optional)",
  "media_type": "string (required) - image|video|audio|file",
  "voice": "boolean (optional) - Send audio as a voice note",
  "reply_to": "string (optional) - message_id of an earlier message to quote",
  "typing": "boolean (optional, default false) - WhatsApp (QR) only. true shows \"typing...\" before sending, so the reply takes up to 3 seconds longer"
}

Example Request

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "media",
  "receiver": "628123456789",
  "url": "https://example.com/image.jpg",
  "caption": "Check out this image!",
  "media_type": "image"
}

Media Types

  • image: JPG, PNG images
  • video: MP4, 3GP videos
  • audio: MP3, OGG, AAC audio files
  • file: PDF, DOC, XLS, and other documents

Success Response (200 OK)

{
  "message": "Media sent.",
  "data": { "message_id": "3EB0C767D0B1A2F4E5A9B3" }
}

Send Buttons

Send a message with up to 3 interactive buttons.

Unofficial and Official channels. Unofficial channels support up to 3 buttons of any type. Official (Cloud API) channels support up to 3 quick_reply buttons, or a single url button, with labels of at most 20 characters, a message of at most 1024 characters and a footer of at most 60. Anything else is rejected with validation_failed, and a message to an Official channel can only be sent within 24 hours after the customer's last message. Other channel types are rejected with unsupported_channel. Buttons on Unofficial channels run over the unofficial WhatsApp Web protocol, so WhatsApp does not guarantee them and they may not display on every phone or app version.

POST /api/messages

Request Body

{
  "type": "buttons",
  "receiver": "string (required) - Phone number with country code (e.g., 628123456789)",
  "message": "string (required) - Message content",
  "footer": "string (optional) - Small text shown below the message",
  "typing": "boolean (optional, default false) - WhatsApp (QR) only. true shows \"typing...\" before sending, so the reply takes up to 3 seconds longer",
  "buttons": [
    {
      "type": "string (required) - quick_reply|url|call|copy",
      "text": "string (required) - Button label, max 25 characters (20 on Official channels)",
      "value": "string - Required for url, call and copy. Optional for quick_reply"
    }
  ]
}

Button Types

  • quick_reply: Sends the button back to you as an incoming message. value is an optional reply ID, if empty, the label is used as the ID.
  • url: Opens a link. value must start with http:// or https://.
  • call: Dials a number. value is the phone number with country code (e.g., +628123456789).
  • copy: Copies text to the clipboard. value is the text to copy (e.g., a promo or OTP code).

You can send 1 to 3 buttons per message.

Example Request

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "buttons",
  "receiver": "628123456789",
  "message": "Your order #1024 has shipped. What would you like to do?",
  "footer": "KonekSip Store",
  "buttons": [
    { "type": "quick_reply", "text": "Contact support" },
    { "type": "url", "text": "Track order", "value": "https://example.com/track/1024" },
    { "type": "call", "text": "Call us", "value": "+628123456789" }
  ]
}

Success Response (200 OK)

{
  "message": "Buttons sent.",
  "data": { "message_id": "3EB0C767D0B1A2F4E5A9B3" }
}

Error Response

{
  "code": "unsupported_channel",
  "message": "This channel does not support this action."
}

When a customer taps a quick_reply button, it arrives on your webhook as a message.received event. The button label is in message and the reply ID is in button_reply.id.

Send List

Send a message with a menu of up to 10 choices. The customer taps the button, picks a row, and the choice arrives on your webhook.

Official (Cloud API) channels only. Other channel types are rejected with an unsupported_channel error. Like every free-form message on an Official channel, a list can only be sent within 24 hours after the customer's last message, use a template outside that window.

POST /api/messages

Request Body

{
  "type": "list",
  "receiver": "string (required) - Phone number with country code (e.g., 628123456789)",
  "message": "string (required) - Message content, max 1024 characters",
  "footer": "string (optional) - Small text below the message, max 60 characters",
  "button_text": "string (required) - Label of the button that opens the list, max 20 characters",
  "sections": [
    {
      "title": "string - Section heading, max 24 characters. Required when there is more than one section",
      "rows": [
        {
          "id": "string (required) - Value returned to your webhook, max 200 characters",
          "title": "string (required) - Row title, max 24 characters",
          "description": "string (optional) - Row description, max 72 characters"
        }
      ]
    }
  ]
}

Limits

  • A list has 1 to 10 sections and at most 10 rows in total.
  • Every section needs a title when there is more than one section.
  • The limits on titles and descriptions are WhatsApp's own, a request over a limit is rejected with validation_failed before anything is sent.

Example Request

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "list",
  "receiver": "628123456789",
  "message": "Which topic do you need help with?",
  "footer": "KonekSip Support",
  "button_text": "Choose a topic",
  "sections": [
    {
      "title": "Orders",
      "rows": [
        { "id": "order_status", "title": "Order status", "description": "Where is my package?" },
        { "id": "order_cancel", "title": "Cancel an order" }
      ]
    },
    {
      "title": "Account",
      "rows": [{ "id": "account_login", "title": "Login problem" }]
    }
  ]
}

Success Response (200 OK)

{
  "message": "List sent.",
  "data": { "message_id": "wamid.HBgM..." }
}

When the customer picks a row it arrives on your webhook as a message.received event. The row title is in message and the row id is in button_reply.id.

Send Template

Send a pre-approved WhatsApp message template. Outside Meta's 24-hour customer service window this is the only way to start a conversation on an Official channel.

Official channels only. Requests for Unofficial channels are rejected with a wrong_channel_type error. Create and approve templates in the dashboard first.

POST /api/messages

Request Body

{
  "type": "template",
  "receiver": "string (required) - Phone number with country code (e.g., 628123456789)",
  "template_name": "string (required) - Name of the approved template",
  "language_code": "string (required) - e.g., en_US, id",
  "components": "array (optional) - Template parameters in Meta's components format",
  "otp_code": "string (optional) - One-time code for an authentication template, up to 15 characters. Replaces components.",
  "header_format": "string (optional) - IMAGE|VIDEO|DOCUMENT, use together with media",
  "media": "string (optional) - Public URL of the header media"
}

Example Request

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "template",
  "receiver": "628123456789",
  "template_name": "order_shipped",
  "language_code": "en_US",
  "components": [
    {
      "type": "body",
      "parameters": [
        { "type": "text", "text": "#1024" }
      ]
    }
  ]
}

Example Request: one-time code

POST /api/messages
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "type": "template",
  "receiver": "628123456789",
  "template_name": "login_code",
  "language_code": "id",
  "otp_code": "482913"
}

For an authentication template, send only otp_code: KonekSip puts the code in the message body and in the Copy code button, so components is not needed. The code can have up to 15 characters. It is kept masked as ****** in the message history and the inbox.

Success Response (200 OK)

{
  "message": "Template sent.",
  "data": { "message_id": "wamid.HBgM..." }
}

Send Email

Send an email from an Email channel. The channel only needs its SMTP server for this, add an IMAP server to also receive replies in the inbox. Email channels use this endpoint instead of /api/messages.

POST /api/emails

Request Body

{
  "to": "string | string[] (required) - Recipient address, or up to 50",
  "subject": "string (required) - Single line, up to 255 characters",
  "bcc": "string | string[] (optional)",
  "cc": "string | string[] (optional)",
  "reply_to": "string (optional) - Address replies should be sent to (Reply-To header)",
  "in_reply_to": "string (optional) - message_id of an earlier email, keeps the thread",
  "html": "string - HTML body (optional when text is given)",
  "text": "string - Plain text body (text or html is required)",
  "attachments": [
    { "url": "https://example.com/invoice.pdf", "filename": "invoice.pdf" }
  ]
}

Example Request

POST /api/emails
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "to": "[email protected]",
  "subject": "Your order has shipped",
  "text": "Hi! Your order is on its way."
}

Success Response (200 OK)

{
  "message": "Email accepted.",
  "data": { "message_id": "<[email protected]>" }
}

Notes

  • An email sent to several addresses appears in each recipient's conversation in the inbox. Bcc recipients are not shown.
  • Up to 5 attachments, each fetched from a public URL.
  • 200 means the mail server accepted the email. Delivery failures (bounces) are not reported.
  • Every other endpoint rejects Email channels.

Reply to a Comment

Answer a comment on a post of a Messenger or Instagram channel, publicly under the comment or as one direct message to the person who wrote it. The comment id comes from the comment.received webhook event, or from a message of type comment in List Messages.

POST /api/comments/{comment_id}/replies

Request Body

{
  "message": "string (required) - up to 2000 characters",
  "private": "boolean (optional) - true sends a direct message instead of a public reply"
}

Example Request

POST /api/comments/17900000000000000/replies
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "message": "Thanks! We will send the details by message.",
  "private": true
}

Success Response (200 OK)

{
  "message": "Private reply sent.",
  "data": { "message_id": "string" }
}

Notes

  • Meta allows one private reply per comment, and only within 7 days of the comment.
  • A public reply is saved in the conversation as a message of type comment, a private one as a normal text message.
  • Only Messenger and Instagram channels accept this endpoint.

Mark Chat as Read

Send a read receipt (blue ticks) for the latest message the customer sent in a chat.

POST /api/chats/{receiver}/read-receipts

Example Request

POST /api/chats/628123456789/read-receipts
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "Chat marked as read."
}

React to Message

Add an emoji reaction to a message.

PUT /api/messages/{message_id}/reaction

Request Body

{
  "emoji": "string (required) - One emoji, or an empty string to remove your reaction"
}

Example Request

PUT /api/messages/3EB0C767D0B1A2F4E5/reaction
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "emoji": "👍"
}

Success Response (200 OK)

{
  "message": "Reaction added."
}

Edit Message

Change the text of a message you sent.

Unofficial channels only. Only messages sent by this channel can be edited. {message_id} is the id of a message in your channel, encode it for use in a URL.

PATCH /api/messages/{message_id}

Request Body

{
  "message": "string (required) - The new text"
}

Example Request

PATCH /api/messages/3EB0C767D0B1A2F4E5
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "message": "Hello! This is the corrected text."
}

Success Response (200 OK)

{
  "message": "Message edited."
}

Delete Message

Delete a message for everyone, or only on your side.

Unofficial channels only. Deleting for everyone only works on messages sent by this channel, a message from someone else returns not_allowed.

DELETE /api/messages/{message_id}

Query Parameters

  • scope - everyone (default) or me (remove it from this channel only)
  • delete_media - true to also delete the stored media file, only with scope=me

Example Request

DELETE /api/messages/3EB0C767D0B1A2F4E5?scope=everyone
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "Message deleted for everyone."
}

List Conversations

Read your conversations, messages, contacts and templates. Lists are paginated: send limit (1 to 100) and pass the next_cursor of the previous page to get the next one. next_cursor is null on the last page. These endpoints need an active subscription, but the channel does not have to be connected.

The conversations of this channel, most recently active first. Group chats are included.

GET /api/conversations

Query Parameters

  • status - optional, open, pending or resolved.
  • limit - optional, 1 to 100, default 20.
  • cursor - optional, the next_cursor of the previous page.

Example Request

GET /api/conversations?status=open&limit=20
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "OK",
  "data": [
    {
      "receiver": "628123456789",
      "name": "Dewi Lestari",
      "status": "open",
      "priority": "high",
      "assigned_to": { "id": 3, "name": "Sari Agent" },
      "last_message": {
        "id": "3EB0C767D0B1A2F4E5A9B3",
        "conversation": "628123456789",
        "from_me": false,
        "type": "text",
        "text": "Hello, is the order shipped?",
        "status": "read",
        "timestamp": 1735689600
      }
    }
  ],
  "next_cursor": "1735689600_4821"
}

receiver is the phone number , the page-scoped ID (Messenger, Instagram), the email address or the group ID . Use it in the next endpoint. last_message has the same shape as a message.

List Messages

The messages of one conversation, newest first.

GET /api/conversations/{receiver}/messages

Query Parameters

  • limit - optional, 1 to 100, default 50.
  • before - optional, the next_cursor of the previous page, for older messages.

Example Request

GET /api/conversations/628123456789/messages?limit=50
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "OK",
  "data": [
    {
      "id": "3EB0C767D0B1A2F4E5A9B3",
      "conversation": "628123456789",
      "from_me": false,
      "type": "text",
      "text": "Hello, is the order shipped?",
      "status": "read",
      "timestamp": 1735689600
    }],
  "next_cursor": "4790"
}

A conversation that does not exist returns an empty data list.

Get a Message

One message by its ID, for example the message_id returned when you sent it, to read its current delivery status.

GET /api/messages/{id}

Example Request

GET /api/messages/3EB0C767D0B1A2F4E5
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "OK",
  "data": {
    "id": "3EB0C767D0B1A2F4E5A9B3",
    "conversation": "628123456789",
    "from_me": false,
    "type": "image",
    "text": "My receipt",
    "media": { "url": "https://cdn.example.com/receipt.jpg", "mimetype": "image/jpeg" },
    "status": "read",
    "timestamp": 1735689600
  }
}

Message Fields

  • type - text, image, video, audio, sticker, file, interactive (buttons and lists), template, email or other.
  • text - the text, the caption of a media message, the template name or the email body.
  • media - only on media messages: url and mimetype.
  • email - only on emails: subject.
  • status - unread, read, sent, delivered or failed, with error_reason when it failed.
  • timestamp - Unix epoch seconds.

An unknown ID returns not_found (404).

List Contacts

The customers of your organization, oldest first. This is the same customer list you manage under Customers.

GET /api/contacts

Query Parameters

  • number - optional, exact phone number, digits only.
  • search - optional, matches name, number, email and company.
  • limit - optional, 1 to 100, default 50.
  • cursor - optional, the next_cursor of the previous page.

Example Request

GET /api/contacts?number=628123456789
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "OK",
  "data": [
    {
      "id": 12,
      "number": "628123456789",
      "name": "Dewi Lestari",
      "email": "[email protected]",
      "company": "PT Maju",
      "status": "customer",
      "tags": ["vip"],
      "custom_fields": { "order_id": "A-77" },
      "opted_out": false,
      "created_at": 1735600000
    }
  ],
  "next_cursor": null
}

custom_fields can be used as variables in campaigns, for example {order_id}. opted_out customers are left out of campaigns.

List Templates

The message templates of an Official (Cloud API) channel, to use with Send Template. Only approved templates are returned unless you pass all=true. Other channel types return wrong_channel_type.

GET /api/templates

Example Request

GET /api/templates
Authorization: Bearer YOUR_API_KEY

Success Response (200 OK)

{
  "message": "OK",
  "data": [
    { "id": "1234567890", "name": "order_update", "language": "en", "status": "APPROVED", "category": "UTILITY" }
  ]
}

The exact fields of each template are the ones Meta returns for it.

Channel Status

Get the channel that owns the API key, including whether it is connected right now. Use it to check a channel before you send.

GET /api/channel

Success Response

{
  "message": "OK",
  "data": {
    "id": "uuid-of-the-channel",
    "name": "My Channel",
    "address": "628123456789",
    "type": "whatsapp_qr",
    "status": "connected",
    "verified": false,
    "verified_name": null
  }
}

status is connected or disconnected. type is whatsapp_qr (Unofficial, QR code), whatsapp_cloud (Official, Cloud API) , email, messenger or instagram, and address is the WhatsApp number or the email address . verified and verified_name show whether the connected number has registered a WhatsApp Business display name, this is not Meta's official verified ("Green Tick") badge. Both are null while the channel is disconnected.

Webhook

Configure a webhook URL to receive incoming WhatsApp messages in real-time. When someone sends a message to your connected WhatsApp number, the system will automatically send the message data to your webhook endpoint.

Important: Your webhook URL must be publicly accessible and accept POST requests with JSON payload. Delivery is retried up to 5 times with backoff if your endpoint is unreachable or errors out.

Setting Up Webhook

1. Configure Webhook URL

In your channel settings, add your webhook URL:

  • Click the settings icon on your channel
  • Enter your webhook URL (e.g., https://yourdomain.com/webhook)
  • Save the settings

Delivery

  • Each event is sent as a POST with a JSON body. Answer with any 2xx status, the response body is ignored.
  • Headers on every request: X-Webhook-Id (same as id), X-Webhook-Event (same as type), X-Webhook-Timestamp (same as created_at) and User-Agent: KonekSip-Webhook/1.0.
  • A failed delivery is retried 5 more times, 1 second, 5 seconds, 30 seconds, 2 minutes and 10 minutes apart, and each attempt times out after 10 seconds.
  • The same id is sent again on a retry, so use it to ignore duplicates.
  • The order of delivery is not guaranteed, a retried event can arrive after a newer one. Sort events by created_at.
  • Redirects are not followed, and URLs that point to private or internal addresses are rejected.

Incoming Message Format

When a message is received, your webhook will receive a POST request with the following data. Every event uses the same envelope: id, type, created_at, channel_id and data.

POST https://yourdomain.com/webhook type: message.received

Example: Text Message

{
  "id": "evt_5b9f6a0e-3c1d-4c0e-9a77-1f2a8d4e6b10",
  "type": "message.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "3EB0...",
    "timestamp": 1735689600,
    "from": "628123456789",
    "participant": null,
    "is_group": false,
    "is_me": false,
    "message": "Hello! I need information about your products.",
    "media": null,
    "button_reply": null
  }
}

Example: Media Message

{
  "id": "evt_8c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "type": "message.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "3EB0...",
    "timestamp": 1735689600,
    "from": "628123456789",
    "participant": null,
    "is_group": false,
    "is_me": false,
    "message": "caption text, if any",
    "media": {
      "url": "https://.../inbound/.../file.jpg",
      "mimetype": "image/jpeg",
      "type": "image"
    },
    "button_reply": null
  }
}

Example: WhatsApp username (BSUID)

{
  "id": "evt_8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
  "type": "message.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "wamid.HBgM...",
    "timestamp": 1735689600,
    "from": "ID.13491208655302741918",
    "participant": null,
    "is_group": false,
    "is_me": false,
    "message": "Hello, I use a username",
    "media": null,
    "button_reply": null,
    "user_id": "ID.13491208655302741918",
    "username": "raka.id"
  }
}

user_id is the customer's BSUID and username the WhatsApp username when there is one, both are null on other channels. from is the phone number while Meta shares it, and the BSUID otherwise. The to of a message.status event follows the same rule.

Example: Button Reply

{
  "id": "evt_a1b2c3d4-e5f6-4789-8abc-def012345678",
  "type": "message.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "3EB0...",
    "timestamp": 1735689600,
    "from": "628123456789",
    "participant": null,
    "is_group": false,
    "is_me": false,
    "message": "Contact support",
    "media": null,
    "button_reply": {
      "id": "support",
      "text": "Contact support"
    }
  }
}

Example: Email

{
  "id": "evt_0f9e8d7c-6b5a-4e1b-9d8c-7b6a5f4e3d2c",
  "type": "message.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "[email protected]",
    "timestamp": 1735689600,
    "from": "[email protected]",
    "participant": null,
    "is_group": false,
    "is_me": false,
    "message": "Hi, how much is the pro plan?",
    "media": null,
    "button_reply": null,
    "email": {
      "subject": "Pricing question",
      "from_name": "Jane Doe",
      "attachments": [{ "filename": "spec.pdf", "size": 20481 }]
    }
  }
}

Email channels send one message.received event per incoming email, with the same data fields plus an email object. from is the sender's address, message the plain-text body, and attachments are listed by name and size only. Email channels do not send message.status events.

button_reply is null unless the customer tapped a quick reply button or picked a list row. id is the value you set on the button (or the button label if you left it empty) or the row id, and text is the label or the row title. message also carries it.

Inside data, message_id identifies the message and timestamp (Unix epoch seconds) is when it was sent or received. from/to are plain WhatsApp IDs (no @s.whatsapp.net suffix , and the page-scoped ID on Messenger and Instagram) , for group chats (is_group: true) from is the group's ID rather than a phone number, and participant is the member who wrote the message. participant is null outside groups. media.type is image, video, audio, sticker or file.

POST https://yourdomain.com/webhook type: message.status

Sent whenever a message you sent changes delivery state (sent, delivered, read, or failed for Cloud API).

{
  "id": "evt_3d2c1b0a-9f8e-4d7c-b6a5-4f3e2d1c0b9a",
  "type": "message.status",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "3EB0...",
    "timestamp": 1735689650,
    "to": "628123456789",
    "status": "delivered"
  }
}
POST https://yourdomain.com/webhook type: message.sent

Live chat channels only. Sent whenever a message is sent to a visitor, by a team member, an auto responder, the AI or the API. to is the id of the visitor. When visitor_online is false the visitor does not have the chat open, which is the moment to send a push notification from your own backend (see Mobile apps in the live chat guide).

{
  "id": "evt_8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
  "type": "message.sent",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "message_id": "5f1c0e2a-7d3b-4c9a-8e1f-2b6d4a9c0e3f",
    "timestamp": 1735689600,
    "to": "3f9c2a6e8b1d4570a9e2c4f6",
    "message": "Hi! Yes, it is still available.",
    "visitor_online": false
  }
}

Conversation Events

Besides messages, the webhook receives events about the conversation itself. They use the same envelope. receiver identifies the conversation, in the same format as in the conversations endpoint. assigned_by is null when the conversation was assigned automatically.

POST https://yourdomain.com/webhook type: conversation.assigned

Sent when a conversation is assigned, claimed, released or transferred to another department, by a team member or automatically. assigned_to is null when nobody has it now.

{
  "id": "evt_6c5b4a39-2817-4f6e-9d0c-1b2a3f4e5d6c",
  "type": "conversation.assigned",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "receiver": "628123456789",
    "status": "open",
    "assigned_to": { "id": 3, "name": "Sari Agent" },
    "assigned_by": { "id": 1, "name": "Hel Owner" },
    "department": { "id": 1, "name": "Customer Service" }
  }
}
POST https://yourdomain.com/webhook type: conversation.closed

Sent when a team member marks a conversation as resolved.

{
  "id": "evt_6c5b4a39-2817-4f6e-9d0c-1b2a3f4e5d6c",
  "type": "conversation.closed",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "receiver": "628123456789",
    "status": "resolved",
    "closed_by": { "id": 3, "name": "Sari Agent" },
    "assigned_to": { "id": 3, "name": "Sari Agent" }
  }
}
POST https://yourdomain.com/webhook type: conversation.sla_breached

Sent once when a customer has waited longer than your response time for the conversation's priority (Settings, Organization, SLA). The check runs every minute, so the event can arrive up to a minute late. A customer who writes again after you replied starts a new wait, which can send a new event. waiting_since and due_at are Unix epoch seconds.

{
  "id": "evt_6c5b4a39-2817-4f6e-9d0c-1b2a3f4e5d6c",
  "type": "conversation.sla_breached",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "receiver": "628123456789",
    "kind": "response",
    "priority": "high",
    "status": "open",
    "waiting_since": 1735688400,
    "due_at": 1735690200,
    "target_minutes": 30,
    "assigned_to": null
  }
}
POST https://yourdomain.com/webhook type: comment.received

Sent for Messenger and Instagram channels when someone comments on a post of your Page or Instagram account. The comment is also in the inbox, in the conversation of the person who wrote it. receiver is that person, comment_id is what you answer with Reply to a comment.

{
  "id": "evt_6c5b4a39-2817-4f6e-9d0c-1b2a3f4e5d6c",
  "type": "comment.received",
  "created_at": 1735689601,
  "channel_id": "uuid-of-the-channel",
  "data": {
    "platform": "instagram",
    "receiver": "17841400000000000",
    "name": "dewi.kitchen",
    "comment_id": "17900000000000000",
    "text": "Is size M still in stock?",
    "post": { "id": "17890000000000000", "url": "https://www.instagram.com/p/AbCdEf/" }
  }
}

Best Practices

  • Always check if is_me is false before replying to avoid loops
  • Use HTTPS for your webhook URL for security
  • Implement timeout and error handling in your webhook
  • Log incoming messages for debugging and analytics
  • Consider rate limiting to prevent spam
  • Validate incoming data structure before processing

Errors

Every error is { "code": "...", "message": "..." } with a matching HTTP status. Use code in your logic, message is for people and may change.

HTTP Code Description
400 invalid_json The request body is not valid JSON
413 payload_too_large The request body is too large
415 unsupported_media_type The body must be sent as JSON with Content-Type: application/json
400 unsupported_channel This channel type does not support the action
400 wrong_channel_type The endpoint is not available for this channel type
401 invalid_api_key The API key is missing or wrong
403 subscription_ended The trial or subscription has ended
403 not_allowed The action is not allowed on this resource
404 not_found The endpoint, message or chat does not exist
409 channel_not_connected The channel is stopped or not connected
409 idempotency_in_progress A request with the same Idempotency-Key is still being processed
422 validation_failed The request is invalid, errors lists each field
422 invalid_receiver The receiver is not a valid WhatsApp number
422 reply_window_closed The 24-hour reply window of a Messenger or Instagram conversation has closed
422 channel_misconfigured The channel settings are incomplete
422 verification_failed The credentials could not be verified
422 idempotency_key_reused The Idempotency-Key was already used with a different request
429 rate_limited Too many requests or delivery failures, wait retry_after seconds (also sent as the Retry-After header)
429 quota_exceeded The monthly message limit of your plan is reached, sending resumes next month or after an upgrade
402 insufficient_balance On a number managed by KonekSip, the wallet balance cannot cover a template or a reply that WhatsApp charges. Top up and send again
422 pricing_missing On a number managed by KonekSip, the recipient is outside Indonesia (only Indonesian numbers can be messaged) or no price is set yet. Contact support
500 request_failed The request failed on our side
502 provider_error WhatsApp or Meta rejected the request, message carries their reason

Need Help?

If you have questions or need assistance, our support team is here to help.

Privacy Policy · Terms of Service