REST API dan Webhook WhatsApp yang Dibuat untuk Integrasi
Kirim template dan pesan, sinkronkan kontak, tag, dan percakapan, serta dapatkan webhook bertanda tangan untuk setiap balasan dan tanda terima pengiriman. REST sederhana di /api/v1 dengan kunci API ber-scope, di WhatsApp Business Platform resmi. Tanpa SDK yang perlu dipasang. API tersedia di setiap paket, mulai dari Developer Free dengan 100 pesan API per bulan.
✦ Uji coba gratis 14 hari · Nomor WhatsApp Business Anda sendiri · Meta menagih pesan secara langsung
Satu REST API untuk Seluruh CRM WhatsApp Anda
Autentikasi dengan kunci API workspace (Authorization: Bearer sm_live_…) dan panggil /api/v1. Kirim pesan template, teks, media, dan interaktif; buat, perbarui, dan beri tag pada kontak; baca tag, tahap pipeline, template, dan kampanye; tampilkan, tugaskan, dan selesaikan percakapan; kelola endpoint webhook; serta laporkan penjualan sebagai konversi. Setiap respons sukses memakai amplop {data, meta} yang sama, dan setiap error memiliki kode tetap yang bisa Anda jadikan acuan.
- Kunci dibuat di Developers → API keys dan hanya ditampilkan sekali
- 10 scope, dengan preset Akses penuh, Hanya kirim, Hanya baca, dan Kustom
- Biaya pesan ditagih Meta langsung ke WhatsApp Business Account Anda; API tidak pernah menagih per pesan
Webhook Bertanda Tangan yang Bisa Dipercaya
Daftarkan endpoint HTTPS ke salah satu dari 12 event: pesan diterima, terkirim, sampai, dibaca, dan gagal; kontak dibuat, berhenti berlangganan, dan berlangganan kembali; percakapan ditugaskan dan diselesaikan; perubahan status template; serta kampanye yang selesai. Setiap pengiriman ditandatangani dengan HMAC-SHA256 di header X-SM-Signature, dicoba ulang setelah 1m, 5m, 30m, 2j, 6j, dan 12j, dan tercatat di log pengiriman tempat Anda bisa mengirimnya ulang.
- Dikirim setidaknya sekali: hapus duplikat berdasarkan id event
- Endpoint yang terus gagal (50 kali berturut-turut, atau selama 24 jam) dinonaktifkan dan ditandai
- Ganti secret penandatanganan dengan masa tumpang tindih 24 jam
Detail yang Diandalkan Integrasi
-
Pengiriman idempoten
Kirim Idempotency-Key pada POST. Kunci dan body yang sama dalam 24 jam mengembalikan respons asli dengan Idempotent-Replayed: true, sehingga percobaan ulang tidak pernah mengirim dua kali.
-
Paginasi kursor dan sinkronisasi
Daftar menerima limit (1–100) dan cursor serta mengembalikan nextCursor. Pada kontak dan percakapan, updatedSince hanya mengembalikan yang berubah, dari yang terlama, untuk sinkronisasi bertahap.
-
Referensi Anda sendiri
Tambahkan clientReference seperti id pesanan Anda ke pengiriman. Nilainya kembali di respons, di GET /messages/{id}, dan di setiap webhook terkirim, sampai, dibaca, dan gagal.
-
Kunci dengan hak akses minimal
Beri setiap integrasi kuncinya sendiri dengan scope yang dibutuhkan saja. Webhook yang dibuat dengan sebuah kunci hanya menerima satu kelompok event selama kunci itu memiliki scope baca yang sesuai.
-
Log permintaan dan pengiriman
Lihat 500 permintaan API terakhir dari 7 hari terakhir di dasbor, difilter per kunci atau hanya error, dan kirim ulang webhook mana pun dari log pengiriman endpoint-nya.
-
Referensi OpenAPI
Dokumen OpenAPI JSON di /api/v1/openapi.json dan Swagger UI di /api/v1/docs, dengan scope tiap operasi tercantum sebagai x-scope. Referensi yang sama ada di dasbor pada menu Developers.
Cakupan API
Semua path ada di bawah /api/v1. Untuk saat ini, membuat template dan kampanye hanya bisa di dasbor: API membacanya dan mengirim template yang disetujui satu pesan per permintaan.
| Resource | Endpoint | Scope | Fungsinya |
|---|---|---|---|
| Akun | GET /me, GET /phone-numbers | Kunci valid apa pun | Workspace, paket, scope kunci, batas, dan pemakaian langsung; nomor Anda beserta rating kualitas dan batas pesan |
| Pesan | POST /messages, GET /messages/{id} | messages:send, messages:read | Pesan template (termasuk carousel dan penawaran waktu terbatas), teks, gambar, video, audio, dokumen, dan interaktif (interaktif memerlukan Team atau Business); status, alasan gagal, dan kategori harga Meta |
| Kontak | GET/POST /contacts, GET/PATCH/DELETE /contacts/{idOrPhone} | contacts:read, contacts:write | Buat atau perbarui berdasarkan nomor telepon, tag, atribut kustom, opt-in dan opt-out; memasukkan kontak ke tahap pipeline memerlukan Business |
| Tag dan pipeline | GET /tags, GET /pipeline-stages | contacts:read | Tag dengan jumlah kontak; tahap pipeline lead Anda |
| Template | GET /templates, GET /templates/{id} | templates:read | Hanya baca: template dibuat di dasbor dan ditinjau oleh Meta |
| Percakapan | GET /conversations, GET /conversations/{id}, GET /conversations/{id}/messages, PATCH /conversations/{id} | conversations:read, conversations:write | Riwayat pesan dan windowOpen; tugaskan, lepas tugas, selesaikan, dan buka kembali (membaca berfungsi di setiap paket; perubahan memerlukan Team atau Business) |
| Kampanye | GET /campaigns, GET /campaigns/{id} | campaigns:read | Statistik hanya baca; kampanye dibuat dan dijalankan di dasbor |
| Konversi | POST /conversions | conversions:write | Laporkan penjualan agar kampanye yang mendatangkan pelanggan mendapat kreditnya (Business) |
| Webhook | GET/POST /webhooks, DELETE /webhooks/{id} | webhooks:manage | Mendaftarkan dan membatalkan endpoint (REST hooks) |
Batas API per Paket
Dihitung per workspace, bukan per kunci. Respons yang dihitung membawa header RateLimit-* dan setiap 429 membawa Retry-After. Pesan API per bulan hanya menghitung pesan yang dikirim lewat API; setelah kuota habis, pengiriman mendapat 402 PLAN_MESSAGE_LIMIT_REACHED. Batas pesan dari Meta untuk nomor Anda tetap berlaku.
| Paket | Permintaan / menit | Pengiriman API / jam | Pesan API / bulan | Endpoint webhook |
|---|---|---|---|---|
| Uji coba gratis (14 hari) | 300 | 100 | 1.000 | 5 |
| Developer Free | 60 | 2.000 | 100 | 2 |
| Developer 1K | 120 | 10.000 | 1.000 | 2 |
| Developer 10K | 300 | 10.000 | 10.000 | 2 |
| Developer 100K | 1.200 | 10.000 | 100.000 | 2 |
| Team | 300 | 10.000 | Tanpa batas (penggunaan wajar) | 1 |
| Business | 1.200 | 10.000 | Tanpa batas (penggunaan wajar) | 5 |
Hanya API? Pilih Ukuran Developer
Paket Developer adalah API dengan dasbor hanya-lihat: 1 kursi, 2 endpoint webhook, dan pesan API per bulan seperti di bawah. Harga sudah termasuk GST, dan satu tahun seharga sepuluh bulan. Ukuran berbayar tidak pernah menghentikan template autentikasi.
| Paket | Harga / bulan | Pesan API / bulan | Permintaan / menit |
|---|---|---|---|
| Developer Free | ₹0 | 100 | 60 |
| Developer 1K | ₹100 | 1.000 | 120 |
| Developer 10K | ₹499 | 10.000 | 300 |
| Developer 100K | ₹999 | 100.000 | 1.200 |
Periksa kunci Anda, lalu kirim template
GET /me tidak memerlukan scope. Pengiriman memerlukan messages:send dan template yang disetujui.
# 1. Check your key: workspace, plan, scopes, limits and usage
curl https://waba-api.startmessaging.com/api/v1/me \
-H "Authorization: Bearer sm_live_your_api_key"
# 2. Send an approved template, safe to retry with the same Idempotency-Key
curl -X POST https://waba-api.startmessaging.com/api/v1/messages \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10421-ready" \
-d '{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}' const API = 'https://waba-api.startmessaging.com/api/v1';
const auth = { Authorization: 'Bearer sm_live_your_api_key' };
// 1. Check your key
const me = await fetch(`${API}/me`, { headers: auth }).then((r) => r.json());
console.log(me.data.plan.code, me.data.apiKey.scopes, me.data.limits);
// 2. Send an approved template, safe to retry with the same Idempotency-Key
const res = await fetch(`${API}/messages`, {
method: 'POST',
headers: {
...auth,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10421-ready',
},
body: JSON.stringify({
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}),
});
const { data } = await res.json();
console.log(data.id, data.status, res.headers.get('Idempotent-Replayed')); import requests
API = 'https://waba-api.startmessaging.com/api/v1'
AUTH = {'Authorization': 'Bearer sm_live_your_api_key'}
# 1. Check your key
me = requests.get(f'{API}/me', headers=AUTH).json()['data']
print(me['plan']['code'], me['apiKey']['scopes'], me['limits'])
# 2. Send an approved template, safe to retry with the same Idempotency-Key
res = requests.post(
f'{API}/messages',
headers={**AUTH, 'Idempotency-Key': 'order-10421-ready'},
json={
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
},
)
data = res.json()['data']
print(data['id'], data['status']) <?php
$api = 'https://waba-api.startmessaging.com/api/v1';
$auth = 'Authorization: Bearer sm_live_your_api_key';
// 1. Check your key
$ch = curl_init("$api/me");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [$auth],
]);
$me = json_decode(curl_exec($ch), true)['data'];
echo $me['plan']['code'] . ' ' . implode(',', $me['apiKey']['scopes']) . "\n";
// 2. Send an approved template, safe to retry with the same Idempotency-Key
$body = <<<'JSON'
{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}
JSON;
$ch = curl_init("$api/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
$auth,
'Content-Type: application/json',
'Idempotency-Key: order-10421-ready',
],
CURLOPT_POSTFIELDS => $body,
]);
$data = json_decode(curl_exec($ch), true)['data'];
echo $data['id'] . ' ' . $data['status']; Verifikasi setiap webhook sebelum memercayainya
X-SM-Signature adalah v1=HMAC-SHA256(secret, timestamp + "." + body mentah). Gunakan seluruh secret whsec_ sebagai kunci.
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = express();
const SECRET = process.env.SM_WEBHOOK_SECRET; // the whole whsec_… string
function verify(rawBody, headers) {
const ts = headers['x-sm-timestamp'];
const sigs = headers['x-sm-signature'];
if (!/^\d+$/.test(ts ?? '') || typeof sigs !== 'string') return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5-minute tolerance
const expected = createHmac('sha256', SECRET).update(`${ts}.`).update(rawBody).digest();
return sigs.split(',').some((part) => {
const m = /^v1=([0-9a-f]{64})$/.exec(part.trim());
return m !== null && timingSafeEqual(Buffer.from(m[1], 'hex'), expected);
});
}
app.post('/webhooks/startmessaging', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.headers)) return res.sendStatus(401);
res.sendStatus(200); // answer 2xx within 5 seconds, then do the work
const event = JSON.parse(req.body);
// Delivered at least once: skip event.id if you have already handled it.
if (event.type === 'message.delivered') {
console.log(event.data.message.clientReference, 'delivered');
}
}); import hmac, hashlib, json, os, time
from flask import Flask, request
app = Flask(__name__)
SECRET = os.environ['SM_WEBHOOK_SECRET'].encode() # the whole whsec_… string
def verify(raw_body, ts, sigs):
if not ts or not ts.isdigit() or not sigs:
return False
if abs(time.time() - int(ts)) > 300: # 5-minute tolerance
return False
expected = hmac.new(SECRET, f'{ts}.'.encode() + raw_body, hashlib.sha256).hexdigest()
return any(
hmac.compare_digest(part.strip()[3:], expected)
for part in sigs.split(',') if part.strip().startswith('v1=')
)
@app.post('/webhooks/startmessaging')
def startmessaging_webhook():
raw = request.get_data()
if not verify(raw, request.headers.get('X-SM-Timestamp'), request.headers.get('X-SM-Signature')):
return '', 401
event = json.loads(raw)
# Delivered at least once: skip event['id'] if you have already handled it.
if event['type'] == 'message.delivered':
print(event['data']['message']['clientReference'], 'delivered')
return '', 200 <?php
$secret = getenv('SM_WEBHOOK_SECRET'); // the whole whsec_… string
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_SM_TIMESTAMP'] ?? '';
$sigs = $_SERVER['HTTP_X_SM_SIGNATURE'] ?? '';
function sm_verify(string $raw, string $ts, string $sigs, string $secret): bool
{
if (!ctype_digit($ts) || $sigs === '') return false;
if (abs(time() - (int) $ts) > 300) return false; // 5-minute tolerance
$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
foreach (explode(',', $sigs) as $part) {
$part = trim($part);
if (strncmp($part, 'v1=', 3) === 0 && hash_equals($expected, substr($part, 3))) {
return true;
}
}
return false;
}
if (!sm_verify($raw, $ts, $sigs, $secret)) {
http_response_code(401);
exit;
}
http_response_code(200); // answer 2xx within 5 seconds
$event = json_decode($raw, true);
// Delivered at least once: skip $event['id'] if you have already handled it.
if ($event['type'] === 'message.delivered') {
error_log($event['data']['message']['clientReference'] . ' delivered');
} Laporkan penjualan, kreditkan ke kampanye
Di paket Business, POST /api/v1/conversions mengkreditkan penjualan ke kampanye terakhir yang terkirim ke nomor itu dalam jendela konversi Anda (7 hari kecuali Anda mengubahnya), dan laporan kampanye menjumlahkan nilainya.
# Business plan, scope conversions:write
curl -X POST https://waba-api.startmessaging.com/api/v1/conversions \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919812345678",
"orderId": "ORD-10442",
"value": 2499.5,
"currency": "INR",
"occurredAt": "2026-10-12T10:30:00+05:30"
}'
# 201 = recorded now, 200 = this orderId was reported already const res = await fetch('https://waba-api.startmessaging.com/api/v1/conversions', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.SM_API_KEY, // sm_live_your_api_key
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone: '+919812345678',
orderId: 'ORD-10442', // the same orderId again never counts the sale twice
value: 2499.5,
currency: 'INR',
}),
});
const { data } = await res.json();
console.log(data.created, data.conversion.campaign?.name ?? 'no campaign credited'); import os, requests
res = requests.post(
'https://waba-api.startmessaging.com/api/v1/conversions',
headers={'Authorization': f"Bearer {os.environ['SM_API_KEY']}"}, # sm_live_your_api_key
json={
'phone': '+919812345678',
'orderId': 'ORD-10442',
'value': 2499.5,
'currency': 'INR',
},
)
data = res.json()['data']
campaign = data['conversion']['campaign']
print(data['created'], campaign['name'] if campaign else 'no campaign credited') API WhatsApp — FAQ
Apakah ada SDK?
Tidak. API ini adalah REST sederhana lewat HTTPS dengan JSON, jadi bahasa atau alat apa pun yang bisa membuat permintaan HTTPS dapat dipakai. Dokumentasi menampilkan cURL, Node.js, Python, dan PHP, dan dokumen OpenAPI menjelaskan setiap kolom permintaan dan respons.
Paket mana yang menyertakan API?
Setiap paket, termasuk Developer Free. Paket Developer adalah API dengan dasbor hanya-lihat, yang ukurannya ditentukan oleh pesan API per bulan: Developer Free dengan 100, lalu 1K, 10K, dan 100K. Team dan Business menambahkan kotak masuk bersama, kontak, dan kampanye, tanpa batas pesan API bulanan dalam penggunaan wajar. Uji coba gratis 14 hari mencakup API dengan 1.000 pesan API. Permintaan per menit dan endpoint webhook bergantung pada paket, dan GET /me menampilkan paket, batas, dan hitungan bulan ini.
Bisakah saya membuat template atau kampanye lewat API?
Belum. Template dibuat di dasbor dan ditinjau oleh Meta; kampanye disusun dan dijalankan di wizard kampanye pada dasbor. API membaca keduanya, mengirim template yang disetujui satu pesan per permintaan dengan POST /messages, dan memberi tahu Anda saat kampanye selesai lewat webhook campaign.completed.
Bisakah API mengirim carousel dan penawaran waktu terbatas?
Ya. Untuk template carousel, kirim template.carouselCards dengan satu entri per kartu, berurutan, masing-masing dengan headerMediaUrl (https) serta parameters dan buttonParameters opsional. Untuk penawaran waktu terbatas, isi template.offerExpiresAt dengan waktu ISO 8601 di masa depan, dan WhatsApp menampilkan hitung mundur kepada pelanggan. Kampanye tidak dapat mengirim template ini, jadi template dikirim satu per satu lewat API atau kotak masuk.
Apa yang terjadi saat kuota pesan API bulanan habis?
Paket Developer dan uji coba gratis mencakup sejumlah pesan API per bulan, dihitung mulai tanggal 1; hanya pesan yang dikirim lewat API yang dihitung. Developer Free dan uji coba berhenti di kuotanya. Paket Developer berbayar menghentikan pesan lain pada tiga kali ukurannya dan tidak pernah menghentikan template autentikasi. Pengiriman yang melewati batas mendapat 402 PLAN_MESSAGE_LIMIT_REACHED dengan sent, stopAt, dan resetsAt di detailnya, dan GET /me menampilkan hitungannya sebelum itu terjadi. Team dan Business tidak memiliki batas pesan API bulanan (penggunaan wajar).
Bagaimana cara menghubungkan Zapier, Make, atau Pabbly Connect?
Belum ada aplikasi resmi StartMessaging di marketplace tersebut. Gunakan modul generik mereka: pemicu catch-hook atau webhook kustom untuk menerima event, dan aksi permintaan HTTP dengan kunci API Anda untuk memanggil API. Pembuat aplikasi sendiri bisa berlangganan dan berhenti dengan POST /webhooks dan DELETE /webhooks/{id}.
Apa yang terjadi jika saya mengulang pengiriman?
Dengan Idempotency-Key dan body yang sama dalam 24 jam, Anda menerima respons asli dengan Idempotent-Replayed: true, dan tidak ada yang terkirim dua kali. Memakai ulang kunci dengan body berbeda ditolak dengan IDEMPOTENCY_KEY_REUSED. Setelah 429 atau 5xx, coba lagi dengan kunci yang sama.
Bagaimana cara kerja batas laju?
Batas berlaku per workspace, jadi menambah kunci tidak pernah menambah kapasitas. Permintaan per menit: 60 di Developer Free, 120 di Developer 1K, 300 di Developer 10K, Team, dan uji coba gratis, serta 1.200 di Developer 100K dan Business. Pengiriman API per jam: 10.000 di paket berbayar, 2.000 di Developer Free, dan 100 di uji coba. Satu pengiriman dihitung pada kedua batas, dan pengiriman yang ditolak mengembalikan kuota per jamnya. Respons 429 memberi tahu lama menunggu di Retry-After. Batas pesan Meta untuk nomor Anda tetap berlaku di atasnya.
Apakah StartMessaging menagih per pesan API?
Tidak. StartMessaging adalah Meta Tech Provider: Anda menghubungkan Akun WhatsApp Business Anda sendiri, dan Meta menagih biaya pesan langsung ke akun tersebut dengan tarif Meta. Meta menagih setiap template yang terkirim, termasuk template utilitas bahkan di dalam jendela 24 jam, serta balasan layanan di atas kuota gratis bulanan. StartMessaging menagih paket tetap, tanpa dompet dan tanpa markup per pesan; ukuran paket Developer ditentukan oleh jumlah pesan API per bulan yang disertakan. Kolom pricing pada setiap pesan menunjukkan klasifikasi dari Meta.
Bangun Integrasi WhatsApp Anda
Workspace pertama Anda mendapat uji coba gratis 14 hari; setelahnya, Developer Free tetap mengaktifkan API dengan 100 pesan per bulan. Hubungkan nomor WhatsApp Business Anda sendiri, buat kunci, dan lakukan panggilan pertama.