. Create an API key under API keys.Guide
WhatsApp Status and channels
Post to the WhatsApp Status of a device (the 24-hour stories) or to a WhatsApp channel it owns, right away or at a set time. Everything goes through one endpoint, POST /v1/sessions/{sessionId}/posts, and every post is kept with its result. In the dashboard: Status & Channels. Not available on the Free plan.
Post a status
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": "status",
"type": "text",
"text": "Open late on Friday until 9 pm 馃帀",
"background_color": "#1F4E79",
"font": 6
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"target": "status",
"type": "text",
"text": "Open late on Friday until 9 pm 馃帀",
"background_color": "#1F4E79",
"font": 6
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"target": "status",
"type": "text",
"text": "Open late on Friday until 9 pm 馃帀",
"background_color": "#1F4E79",
"font": 6,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'target' => 'status',
'type' => 'text',
'text' => 'Open late on Friday until 9 pm 馃帀',
'background_color' => '#1F4E79',
'font' => 6,
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"post": {
"id": "01JA6P2T5V8X0Z3B6D9F2H5K8M",
"target": "status",
"type": "text",
"text": "Open late on Friday until 9 pm 馃帀",
"background_color": "#1F4E79",
"font": 6,
"audience": {
"kind": "all"
},
"recipients": 214,
"status": "posted",
"scheduled_at": 1767225600000,
"posted_at": 1767225601200,
"error": null
}
}A text status takes a background_color (#RRGGBB, white text on top) and a font: 0 default, 1 serif, 2 script, 6 bold, 10 typewriter (WhatsApp also knows 7, 8, 9). For an image or a video, upload the file with POST /v1/media/upload and send type image or video with its media_r2_key; text becomes the caption.
Gradients, textures and background music
Besides a solid colour, a text status can have a gradient background ("background": sunset, ocean, forest, berry, night, gold) or a texture over background_color (dots, stripes, grid, paper). WhatsApp's own text status only knows solid colours, so these are drawn as a 1080脳1920 picture with your text and posted as an image status.
A text or image status can also get background music: upload an audio file with POST /v1/media/upload and pass its key as audio_r2_key (optionally audio_start in seconds). The status becomes a video of up to 30 seconds. Use music you have the rights to; links to YouTube or other streaming sites are not accepted.
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": "status",
"type": "text",
"text": "Weekend sale: 30% off!",
"background": "sunset",
"font": 6,
"audio_r2_key": "uploads/3f9c2a7e1b.mp3",
"audio_start": 12
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"target": "status",
"type": "text",
"text": "Weekend sale: 30% off!",
"background": "sunset",
"font": 6,
"audio_r2_key": "uploads/3f9c2a7e1b.mp3",
"audio_start": 12
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"target": "status",
"type": "text",
"text": "Weekend sale: 30% off!",
"background": "sunset",
"font": 6,
"audio_r2_key": "uploads/3f9c2a7e1b.mp3",
"audio_start": 12,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'target' => 'status',
'type' => 'text',
'text' => 'Weekend sale: 30% off!',
'background' => 'sunset',
'font' => 6,
'audio_r2_key' => 'uploads/3f9c2a7e1b.mp3',
'audio_start' => 12,
],
]);
print_r(json_decode((string) $res->getBody(), true));Who sees it
WhatsApp shows a status only to the people it is addressed to, and of those only to the ones who saved your number in their phone. audience chooses them:
{"kind": "all"}(default): everyone this device has chatted with one to one, plus your CRM contacts;{"kind": "chats"}or{"kind": "contacts"}: one of the two;{"kind": "numbers", "numbers": ["+44 7700 900123", 鈥}: exactly these people.
Opted-out people are always left out, and one status goes to at most 2 000 people. GET /v1/sessions/{sessionId}/posts/audience tells you how many each choice reaches. recipients on the post is the number it was addressed to.
Mention everyone (@all)
Add "mention": "all" to a status and everyone it is addressed to is mentioned: each person gets a "mentioned you in a status" notice and can reshare it. Notices go to at most 500 people per status, are sent gradually through the anti-ban queue and count as messages toward your quota, so keep it for news that matters. mentions on the post says how many were queued. Channels do not support mentions (WhatsApp tells followers about new posts itself).
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": "status",
"type": "text",
"text": "Flash sale starts now: 30% off everything until midnight!",
"mention": "all"
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"target": "status",
"type": "text",
"text": "Flash sale starts now: 30% off everything until midnight!",
"mention": "all"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"target": "status",
"type": "text",
"text": "Flash sale starts now: 30% off everything until midnight!",
"mention": "all",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'target' => 'status',
'type' => 'text',
'text' => 'Flash sale starts now: 30% off everything until midnight!',
'mention' => 'all',
],
]);
print_r(json_decode((string) $res->getBody(), true));Schedule ahead
Add scheduled_at (Unix milliseconds, up to 90 days ahead) and the post waits with status: "scheduled". The server posts it on time; if the device is offline then, it is posted when the device reconnects, or marked failed after 6 hours. DELETE /v1/sessions/{sessionId}/posts/{id} cancels a scheduled post, and removes a posted status from WhatsApp for everyone.
Channels
Create a channel owned by the device, or link one it already owns or administers by its invite link:
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Coffee News",
"description": "New beans, offers and opening hours"
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme Coffee News",
"description": "New beans, offers and opening hours"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"name": "Acme Coffee News",
"description": "New beans, offers and opening hours",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'name' => 'Acme Coffee News',
'description' => 'New beans, offers and opening hours',
],
]);
print_r(json_decode((string) $res->getBody(), true));curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels/link" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"link": "https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMn"
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels/link', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"link": "https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMn"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels/link",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"link": "https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMn",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/channels/link', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'link' => 'https://whatsapp.com/channel/0029VaAbCdEfGhIjKlMn',
],
]);
print_r(json_decode((string) $res->getBody(), true));Then post to it with "target": "channel" and its channel_id (the 鈥newsletter id from GET /v1/sessions/{sessionId}/channels):
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": "channel",
"channel_id": "120363000000000099@newsletter",
"type": "text",
"text": "This week only: 20% off all coffee beans."
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"target": "channel",
"channel_id": "120363000000000099@newsletter",
"type": "text",
"text": "This week only: 20% off all coffee beans."
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"target": "channel",
"channel_id": "120363000000000099@newsletter",
"type": "text",
"text": "This week only: 20% off all coffee beans.",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/posts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'target' => 'channel',
'channel_id' => '120363000000000099@newsletter',
'type' => 'text',
'text' => 'This week only: 20% off all coffee beans.',
],
]);
print_r(json_decode((string) $res->getBody(), true));Channel posts can be text, images or videos. WhatsApp does not let the API delete them; remove one in WhatsApp itself. Test devices cannot create or link channels (501 SANDBOX_UNSUPPORTED); they can post statuses.
Results and errors
A post WhatsApp refused, or one with nobody to show it to, is stored as failed with the reason in error and answered with 422. The device must be connected to post now (409 otherwise). The Free plan answers 403 FREE_PLAN_RESTRICTED. GET /v1/sessions/{sessionId}/posts lists the history, newest first.