Sikio API · v1
Send your first WhatsApp template
Start with an approved fixed-text template with no placeholders. Once this works, use the same send endpoint for templates with variables, buttons or optional media.
Before you start
- A Sikio workspace with an active native WhatsApp channel and billing access.
- An approved fixed-text template in that channel’s template library.
- A consenting test recipient in international E.164 format, including the leading
+. - A workspace administrator who can create an API key.
1. Create an integration key
In the dashboard, open Integrations → API keys. Select whatsapp:read and whatsapp:send. Save the API key and the once-shown secret in your server’s secret store.
Use the API environment that owns the workspace. Staging credentials and data are separate from production. Read the authentication guide.
Replace YOUR_API_KEY and YOUR_API_SECRET in the requests below. Each request is complete; no environment setup or JSON file is required. Follow the private-folder, file-permission and shell-history guidance when testing with real credentials.
2. Discover your channel
curl --fail-with-body \
--user 'YOUR_API_KEY:YOUR_API_SECRET' \
--write-out '\nHTTP %{http_code}\n' \
'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels'const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');
const response = await fetch("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels", {
method: 'GET',
headers: {
Authorization: 'Basic ' + credentials,
},
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(response.status, text ? JSON.parse(text) : null);import base64, json
import urllib.request, urllib.error
credentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()
headers = {'Authorization': 'Basic ' + credentials}
request = urllib.request.Request("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels", method='GET', headers=headers)
try:
with urllib.request.urlopen(request, timeout=30) as response:
text = response.read().decode()
print(response.status, json.loads(text) if text else None)
except urllib.error.HTTPError as error:
raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')Choose a channel’s numeric data[].id and replace CHANNEL_ID in the next request with that value. Use the Sikio ID, rather than phone_number_id.
3. Select an approved template
Replace CHANNEL_ID with your channel’s id.
curl --fail-with-body \
--user 'YOUR_API_KEY:YOUR_API_SECRET' \
--write-out '\nHTTP %{http_code}\n' \
'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates'const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');
const response = await fetch("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates", {
method: 'GET',
headers: {
Authorization: 'Basic ' + credentials,
},
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(response.status, text ? JSON.parse(text) : null);import base64, json
import urllib.request, urllib.error
credentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()
headers = {'Authorization': 'Basic ' + credentials}
request = urllib.request.Request("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates", method='GET', headers=headers)
try:
with urllib.request.urlopen(request, timeout=30) as response:
text = response.read().decode()
print(response.status, json.loads(text) if text else None)
except urllib.error.HTTPError as error:
raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')Choose an APPROVED fixed-text template with no variable placeholders or media header, such as Your order is ready for collection.
Use its id below. Follow cursor pagination if paging.has_more is true. Other template types use the fields defined by their components.
4. Send the template
Replace CHANNEL_ID with your channel’s id. Use your recipient and approved template ID. Add variables or media only when that template needs them. Replace UNIQUE_MESSAGE_KEY for each new message; keep it unchanged when retrying that message.
curl --fail-with-body \
--user 'YOUR_API_KEY:YOUR_API_SECRET' \
--write-out '\nHTTP %{http_code}\n' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: UNIQUE_MESSAGE_KEY' \
--data-binary @- \
'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages' <<'JSON'
{
"recipient": "+254700000001",
"template_id": "YOUR_APPROVED_TEMPLATE_ID"
}
JSONconst credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');
const response = await fetch("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages", {
method: 'POST',
headers: {
Authorization: 'Basic ' + credentials,
'Content-Type': 'application/json',
'Idempotency-Key': 'UNIQUE_MESSAGE_KEY',
},
body: JSON.stringify({
"recipient": "+254700000001",
"template_id": "YOUR_APPROVED_TEMPLATE_ID"
}),
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(response.status, text ? JSON.parse(text) : null);import base64, json
import urllib.request, urllib.error
credentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()
headers = {'Authorization': 'Basic ' + credentials}
body = json.dumps({
"recipient": "+254700000001",
"template_id": "YOUR_APPROVED_TEMPLATE_ID"
}).encode()
headers['Content-Type'] = 'application/json'
headers['Idempotency-Key'] = 'UNIQUE_MESSAGE_KEY'
request = urllib.request.Request("https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages", method='POST', headers=headers, data=body)
try:
with urllib.request.urlopen(request, timeout=30) as response:
text = response.read().decode()
print(response.status, json.loads(text) if text else None)
except urllib.error.HTTPError as error:
raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')Run one language example. On a retry, reuse the original credential, key, channel and identical body.
5. Track the request
Store the response’s numeric id. This is the integration request ID. HTTP 202 and status: accepted mean provider acceptance, not customer delivery.
Replace REQUEST_ID with the send response’s id.
curl --fail-with-body \
--user 'YOUR_API_KEY:YOUR_API_SECRET' \
--write-out '\nHTTP %{http_code}\n' \
'https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID'const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');
const response = await fetch("https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID", {
method: 'GET',
headers: {
Authorization: 'Basic ' + credentials,
},
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(response.status, text ? JSON.parse(text) : null);import base64, json
import urllib.request, urllib.error
credentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()
headers = {'Authorization': 'Basic ' + credentials}
request = urllib.request.Request("https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID", method='GET', headers=headers)
try:
with urllib.request.urlopen(request, timeout=30) as response:
text = response.read().decode()
print(response.status, json.loads(text) if text else None)
except urllib.error.HTTPError as error:
raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')6. Open the conversation in Sikio
Find the recipient in the same workspace’s inbox. The outbound message appears with its API integration source, reference and supplied context. Replies stay in that conversation. conversation_id and message_id identify the persisted conversation and message.
Next: template parameters · delivery, errors and retries.
Next guideAuthentication and API keys →