. Create an API key under API keys.API reference · Integrations
Online stores
Shopify and WooCommerce order notifications: connect a store, edit the messages, test, and the signed webhook the store calls.
List connected stores
/v1/store-integrationsDashboard loginConnected online stores. Each store sends its order webhooks to webhook_url; the customer gets a WhatsApp message from session_id: new order (order_created), payment received (order_paid), shipped with tracking (order_shipped) and, for Shopify, a reminder for an unfinished checkout (abandoned_checkout, after abandoned_delay_min, cancelled when the order arrives). Each message goes once per order through the scheduled queue, so sending limits and opt-outs apply. Owner login only.
Responses
- 200Stores
Response fields
itemsarray of objectrequireditems[].idstringrequireditems[].platformstringrequireditems[].namestringrequiredStore name used as {store}items[].session_idstring | nullrequiredThe WhatsApp number that sendsitems[].enabledbooleanrequireditems[].has_secretbooleanrequiredWhether the webhook signing secret is saved (never returned)items[].webhook_urlstringrequiredPaste into the store's webhook settingsitems[].templatesobjectrequireditems[].templates.order_createdobjectrequireditems[].templates.order_created.enabledbooleanrequireditems[].templates.order_created.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outitems[].templates.order_paidobjectrequireditems[].templates.order_paid.enabledbooleanrequireditems[].templates.order_paid.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outitems[].templates.order_shippedobjectrequireditems[].templates.order_shipped.enabledbooleanrequireditems[].templates.order_shipped.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outitems[].templates.abandoned_checkoutobjectrequireditems[].templates.abandoned_checkout.enabledbooleanrequireditems[].templates.abandoned_checkout.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outitems[].abandoned_delay_minintegerrequireditems[].abandoned_only_marketingbooleanrequireditems[].shop_domainstring | nullrequiredAs reported by the store's webhooksitems[].last_event_atinteger | nullrequireditems[].last_errorstring | nullrequireditems[].created_atintegerrequiredeventsarray of stringrequired
- 403Not the owner
curl "https://api.chatkomodo.com/v1/store-integrations" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/store-integrations', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/store-integrations",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/store-integrations', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"items": [
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"platform": "shopify",
"name": "Example",
"session_id": "YOUR_DEVICE_ID",
"enabled": true,
"has_secret": true,
"webhook_url": "https://example.com/webhooks/chatkomodo",
"templates": {
"order_created": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_paid": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_shipped": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"abandoned_checkout": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
}
},
"abandoned_delay_min": 1,
"abandoned_only_marketing": true,
"shop_domain": "string",
"last_event_at": 1767225600000,
"last_error": "string",
"created_at": 1767225600000
}
],
"events": [
"message.received"
]
}Connect a store
/v1/store-integrationsConnect a store (at most 5). Shopify: create the webhooks in Shopify (Settings → Notifications → Webhooks, JSON) with webhook_url, then save the signing secret Shopify shows there with PUT … {secret}. WooCommerce: a strong secret is generated and returned once — paste it as the webhook secret in WooCommerce (Settings → Advanced → Webhooks, topics Order created / Order updated). Audited as store.connect.
Request body
platformstringrequirednamestringrequiredsession_idstringsecretstring
Responses
- 201Connected
Response fields
integrationobjectrequiredintegration.idstringrequiredintegration.platformstringrequiredintegration.namestringrequiredStore name used as {store}integration.session_idstring | nullrequiredThe WhatsApp number that sendsintegration.enabledbooleanrequiredintegration.has_secretbooleanrequiredWhether the webhook signing secret is saved (never returned)integration.webhook_urlstringrequiredPaste into the store's webhook settingsintegration.templatesobjectrequiredintegration.templates.order_createdobjectrequiredintegration.templates.order_created.enabledbooleanrequiredintegration.templates.order_created.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.order_paidobjectrequiredintegration.templates.order_paid.enabledbooleanrequiredintegration.templates.order_paid.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.order_shippedobjectrequiredintegration.templates.order_shipped.enabledbooleanrequiredintegration.templates.order_shipped.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.abandoned_checkoutobjectrequiredintegration.templates.abandoned_checkout.enabledbooleanrequiredintegration.templates.abandoned_checkout.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.abandoned_delay_minintegerrequiredintegration.abandoned_only_marketingbooleanrequiredintegration.shop_domainstring | nullrequiredAs reported by the store's webhooksintegration.last_event_atinteger | nullrequiredintegration.last_errorstring | nullrequiredintegration.created_atintegerrequiredsecretstringWooCommerce: the generated signing secret, shown only now
- 400
VALIDATION_FAILED - 402
LIMIT_REACHED - 403Not the owner
curl -X POST "https://api.chatkomodo.com/v1/store-integrations" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "woocommerce",
"name": "Kopi Kita",
"session_id": "YOUR_DEVICE_ID"
}'const res = await fetch('https://api.chatkomodo.com/v1/store-integrations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"platform": "woocommerce",
"name": "Kopi Kita",
"session_id": "YOUR_DEVICE_ID"
}),
});
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/store-integrations",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"platform": "woocommerce",
"name": "Kopi Kita",
"session_id": "YOUR_DEVICE_ID",
},
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/store-integrations', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'platform' => 'woocommerce',
'name' => 'Kopi Kita',
'session_id' => 'YOUR_DEVICE_ID',
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"integration": {
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"platform": "shopify",
"name": "Example",
"session_id": "YOUR_DEVICE_ID",
"enabled": true,
"has_secret": true,
"webhook_url": "https://example.com/webhooks/chatkomodo",
"templates": {
"order_created": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_paid": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_shipped": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"abandoned_checkout": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
}
},
"abandoned_delay_min": 1,
"abandoned_only_marketing": true,
"shop_domain": "string",
"last_event_at": 1767225600000,
"last_error": "string",
"created_at": 1767225600000
},
"secret": "a-long-random-secret-of-32-characters-or-more"
}Update a store connection
/v1/store-integrations/{id}Change any of name, session_id (null = none), secret (blank keeps it), enabled, templates (any subset of the four messages), abandoned_delay_min (15–2880), abandoned_only_marketing (remind only buyers who accepted marketing). Audited as store.update.
Path parameters
idstringrequired
Request body
namestringsession_idstring | nullsecretstringenabledbooleantemplatesobjecttemplates.order_createdobjecttemplates.order_created.enabledbooleanrequiredtemplates.order_created.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outtemplates.order_paidobjecttemplates.order_paid.enabledbooleanrequiredtemplates.order_paid.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outtemplates.order_shippedobjecttemplates.order_shipped.enabledbooleanrequiredtemplates.order_shipped.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outtemplates.abandoned_checkoutobjecttemplates.abandoned_checkout.enabledbooleanrequiredtemplates.abandoned_checkout.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outabandoned_delay_minintegerabandoned_only_marketingboolean
Responses
- 200Saved
Response fields
integrationobjectrequiredintegration.idstringrequiredintegration.platformstringrequiredintegration.namestringrequiredStore name used as {store}integration.session_idstring | nullrequiredThe WhatsApp number that sendsintegration.enabledbooleanrequiredintegration.has_secretbooleanrequiredWhether the webhook signing secret is saved (never returned)integration.webhook_urlstringrequiredPaste into the store's webhook settingsintegration.templatesobjectrequiredintegration.templates.order_createdobjectrequiredintegration.templates.order_created.enabledbooleanrequiredintegration.templates.order_created.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.order_paidobjectrequiredintegration.templates.order_paid.enabledbooleanrequiredintegration.templates.order_paid.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.order_shippedobjectrequiredintegration.templates.order_shipped.enabledbooleanrequiredintegration.templates.order_shipped.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.templates.abandoned_checkoutobjectrequiredintegration.templates.abandoned_checkout.enabledbooleanrequiredintegration.templates.abandoned_checkout.textstringrequiredPlaceholders: {name} {order} {total} {items} {status} {courier} {tracking_number} {tracking_url} {checkout_url} {store}; a line whose placeholders are all empty is left outintegration.abandoned_delay_minintegerrequiredintegration.abandoned_only_marketingbooleanrequiredintegration.shop_domainstring | nullrequiredAs reported by the store's webhooksintegration.last_event_atinteger | nullrequiredintegration.last_errorstring | nullrequiredintegration.created_atintegerrequired
- 400
VALIDATION_FAILED - 403Not the owner
- 404Not found
curl -X PUT "https://api.chatkomodo.com/v1/store-integrations/ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templates": {
"order_paid": {
"enabled": true,
"text": "Hi {name}, we received {total} for order {order}. Thank you!"
}
},
"abandoned_delay_min": 60
}'const res = await fetch('https://api.chatkomodo.com/v1/store-integrations/ID', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"templates": {
"order_paid": {
"enabled": true,
"text": "Hi {name}, we received {total} for order {order}. Thank you!"
}
},
"abandoned_delay_min": 60
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.put(
"https://api.chatkomodo.com/v1/store-integrations/ID",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"templates": {
"order_paid": {
"enabled": True,
"text": "Hi {name}, we received {total} for order {order}. Thank you!",
},
},
"abandoned_delay_min": 60,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('PUT', 'https://api.chatkomodo.com/v1/store-integrations/ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'templates' => [
'order_paid' => [
'enabled' => true,
'text' => 'Hi {name}, we received {total} for order {order}. Thank you!',
],
],
'abandoned_delay_min' => 60,
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"integration": {
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"platform": "shopify",
"name": "Example",
"session_id": "YOUR_DEVICE_ID",
"enabled": true,
"has_secret": true,
"webhook_url": "https://example.com/webhooks/chatkomodo",
"templates": {
"order_created": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_paid": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"order_shipped": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
},
"abandoned_checkout": {
"enabled": true,
"text": "Hi Sam, your order has shipped."
}
},
"abandoned_delay_min": 1,
"abandoned_only_marketing": true,
"shop_domain": "string",
"last_event_at": 1767225600000,
"last_error": "string",
"created_at": 1767225600000
}
}Disconnect a store
/v1/store-integrations/{id}Disconnect a store: its link stops working and waiting checkout reminders are dropped. Audited as store.disconnect.
Path parameters
idstringrequired
Responses
- 200Disconnected
Response fields
okbooleanrequired
- 403Not the owner
- 404Not found
curl -X DELETE "https://api.chatkomodo.com/v1/store-integrations/ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/store-integrations/ID', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.delete(
"https://api.chatkomodo.com/v1/store-integrations/ID",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('DELETE', 'https://api.chatkomodo.com/v1/store-integrations/ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true
}List recent store events
/v1/store-integrations/{id}/eventsThe last 50 webhooks from the store and what was done: sent, scheduled / rescheduled / cancelled (checkout reminders), duplicate, off (message switched off), no_phone, no_session, not_marketing, ignored. Kept 30 days.
Path parameters
idstringrequired
Responses
- 200Newest first
Response fields
itemsarray of objectrequireditems[].idintegerrequireditems[].topicstringrequireditems[].refstring | nullrequiredOrder number or checkoutitems[].outcomestringrequireditems[].detailstring | nullrequireditems[].created_atintegerrequired
- 403Not the owner
- 404Not found
curl "https://api.chatkomodo.com/v1/store-integrations/ID/events" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/store-integrations/ID/events', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/store-integrations/ID/events",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/store-integrations/ID/events', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"items": [
{
"id": 1,
"topic": "string",
"ref": "string",
"outcome": "string",
"detail": "string",
"created_at": 1767225600000
}
]
}Send a test store message
/v1/store-integrations/{id}/testSend one message (event, default order_created) filled with a sample order to phone, through the queue. 10 per hour per store.
Path parameters
idstringrequired
Request body
phonestringrequiredeventstring
Responses
- 200Queued
Response fields
okbooleanrequiredscheduled_idstringrequired
- 400
VALIDATION_FAILED - 403Not the owner
- 404Not found
- 409
NO_SESSION— choose the sending number first - 429Too many tests
curl -X POST "https://api.chatkomodo.com/v1/store-integrations/ID/test" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+447700900123",
"event": "order_shipped"
}'const res = await fetch('https://api.chatkomodo.com/v1/store-integrations/ID/test', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"phone": "+447700900123",
"event": "order_shipped"
}),
});
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/store-integrations/ID/test",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"phone": "+447700900123",
"event": "order_shipped",
},
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/store-integrations/ID/test', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'phone' => '+447700900123',
'event' => 'order_shipped',
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true,
"scheduled_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X"
}Receive a store webhook
/hooks/store/{token}Signed by the storeWhere Shopify / WooCommerce send order webhooks (webhook_url). No bearer token: the body must be signed with the store's secret (HMAC-SHA256, base64) in X-Shopify-Hmac-Sha256 or X-WC-Webhook-Signature; the topic comes from X-Shopify-Topic / X-WC-Webhook-Topic. WooCommerce's address check (webhook_id=<n>) is answered with 200. Shopify topics: orders/create, orders/paid, orders/fulfilled, orders/partially_fulfilled, fulfillments/create, fulfillments/update, checkouts/create, checkouts/update, checkouts/delete, orders/cancelled. WooCommerce: order.created, order.updated (processing = paid, completed = shipped; tracking from Shipment Tracking / AfterShip order meta).
Path parameters
tokenstringrequired
Responses
- 200Handled
Response fields
okbooleanrequiredoutcomestring
- 401
BAD_SIGNATUREorNO_SECRET - 404Unknown link
- 429More than 300 a minute
curl -X POST "https://api.chatkomodo.com/hooks/store/YOUR_STORE_TOKEN"const res = await fetch('https://api.chatkomodo.com/hooks/store/YOUR_STORE_TOKEN', {
method: 'POST',
});
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/hooks/store/YOUR_STORE_TOKEN",
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/hooks/store/YOUR_STORE_TOKEN', [
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true,
"outcome": "string"
}