Dokumentasi API
Referensi untuk API publik Whapify: base URL, cara autentikasi, bentuk error, serta endpoint kirim pesan, kontak, grup, dan campaign yang tersedia.
Base URL: https://api.whapify.id/api/v1
Semua request dan respons memakai JSON. Field yang tidak dikenal pada body ditolak dengan kode invalid_json.
Autentikasi
API publik memakai API key per tenant, bukan sesi dashboard. Buat key di dashboard pada halaman API Keys, lalu kirim pada setiap permintaan lewat header Authorization.
Authorization: Bearer wh_live_••••Key hanya ditampilkan sekali saat dibuat. Simpan di tempat yang aman; kalau hilang atau bocor, buat key baru lalu cabut yang lama dari dashboard.
Key dibuat dengan sebagian dari empat izin: send, read, contacts, campaigns. Kalau field ini tidak diisi, key mendapat keempatnya. Key yang tidak punya izin yang dibutuhkan endpoint akan gagal dengan forbidden, menyebutkan izin yang kurang.
Key bisa diberi masa berlaku saat dibuat, dan bisa dicabut dari dashboard kapan saja. Key yang sudah dicabut atau kedaluwarsa gagal dengan error unauthorized yang sama seperti key yang tidak pernah ada, jadi key yang bocor tidak bisa dibedakan dari key yang tidak dikenal. Pencabutan tidak bisa dibatalkan; satu-satunya jalan kembali adalah membuat key baru.
Konvensi
ID berupa UUID. Timestamp memakai format RFC3339 dalam UTC. Setiap respons memakai Cache-Control: no-store.
Idempotensi
POST /wa/send menerima header Idempotency-Key, disimpan selama 24 jam. Mengirim key yang sama dengan body yang sama lagi akan mengembalikan respons pertama persis sama, disertai header respons Idempotency-Replayed, alih-alih mengantre pesan itu lagi.
Idempotency-Key: key-yang-anda-buat-sekali-per-pengiriman
Idempotency-Replayed: true # saat diulang dengan body yang samaKey yang sama dengan body yang berbeda dijawab 422 validation_failed, bukan diulang. Permintaan duplikat yang datang saat permintaan pertama masih diproses dijawab 409 idempotency_in_progress. Respons yang menggambarkan sesuatu yang sementara, seperti account_not_connected, quota_exceeded, error 5xx, atau rate_limited, tidak disimpan, jadi mencoba lagi setelah itu pulih adalah percobaan baru, bukan pengulangan.
Kode error
Setiap kegagalan memakai bentuk envelope yang sama: sebuah kode singkat dan pesan yang aman ditampilkan.
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
already_exists | 409 | Data dengan nilai ini sudah ada. |
invalid_state | 409 | Campaign sedang tidak dalam status yang mengizinkan aksi ini. |
account_not_connected | 409 | Akun WhatsApp yang dipakai mengirim sedang tidak terhubung. |
idempotency_in_progress | 409 | Ada permintaan lain dengan Idempotency-Key ini yang masih diproses. |
quota_exceeded | 402 | Kuota kirim bulanan pada paket Anda sudah habis. |
media_too_large | 413 | Berkas media melebihi batas 20 MB. |
storage_exceeded | 413 | Total penyimpanan media tenant sudah penuh. Hapus sebagian media dulu. |
unsafe_url | 422 | media_url harus tautan https yang bisa diakses publik. |
invalid_signature | 401 | Tanda tangan pada tautan media tidak cocok. |
link_expired | 410 | Tautan media sudah lewat masa berlaku. Baca ulang pesannya untuk dapat tautan baru. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Batas kecepatan
Setiap permintaan /wa/* dibatasi 60 permintaan per menit per API key; melebihi batas ini dijawab dengan 429 rate_limited. Ada batas kedua, 600 permintaan per menit per alamat IP, yang berlaku sebelum autentikasi, dan POST /wa/validate juga berbagi jatah 6 permintaan per menit per tenant dengan pencarian WhatsApp milik dashboard sendiri, karena setiap panggilan di sana adalah round trip ke WhatsApp.
Header respons
| Kode | Keterangan |
|---|---|
X-RateLimit-Limit | Jumlah permintaan yang diperbolehkan pada jendela waktu berjalan. |
X-RateLimit-Remaining | Sisa permintaan pada jendela waktu berjalan. |
Retry-After | Jumlah detik sebelum mencoba lagi, dikirim pada respons 429. |
Ketiga header ini disertakan pada setiap jawaban yang terautentikasi. Respons 401 untuk key yang hilang, dicabut, atau kedaluwarsa tidak membawa satu pun dari header ini.
Endpoint untuk mengirim pesan, membaca riwayat, memvalidasi nomor, mendaftar grup WhatsApp, mengelola kontak, menjalankan campaign, dan mengambil media. Semuanya memakai autentikasi API key.
Kirim pesan
POST /wa/sendMenerima satu pesan teks, media, atau lokasi dan mengantrekannya. Jawabannya 202: pesan diterima, belum terkirim.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
account | uuid | string | Wajib | ID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890. |
account_id | uuid | Opsional | Diterima juga, memakai nama field milik dashboard untuk akun yang sama. |
recipient | string | Wajib | Tujuan: nomor dalam format E.164 tanpa tanda plus, atau ID grup. |
type | text | image | video | audio | document | location | Wajib | text, image, video, audio, document, atau location. |
body | string | Opsional | Isi teks. Opsional untuk pesan location. |
media_id | uuid | Opsional | ID media dari berkas yang sudah diunggah lewat media library di dashboard. Pakai ini atau media_url, tidak keduanya. |
media_url | string | Opsional | URL https publik ke media. Dipakai jika tidak mengirim media_id. |
location.lat | number | Opsional | Lintang, berupa angka. |
location.lng | number | Opsional | Bujur, berupa angka. |
location.name | string | Opsional | Nama lokasi singkat yang tampil bersama pin. |
Contoh permintaan
curl -X POST https://api.whapify.id/api/v1/wa/send \
-H "Authorization: Bearer wh_live_••••" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4711" \
-d '{
"account": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"recipient": "6281234567890",
"type": "text",
"body": "Halo dari Whapify"
}'Respons sukses
202
{
"id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"campaign_id": null,
"direction": "out",
"wa_message_id": null,
"chat_jid": "[email protected]",
"recipient": "6281234567890",
"group_name": null,
"type": "text",
"body": "Halo dari Whapify",
"media": null,
"location": null,
"status": "queued",
"error": null,
"scheduled_at": null,
"created_at": "2026-09-01T09:12:00Z",
"sent_at": null,
"updated_at": "2026-09-01T09:12:00Z"
}Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
account_not_connected | 409 | Akun WhatsApp yang dipakai mengirim sedang tidak terhubung. |
idempotency_in_progress | 409 | Ada permintaan lain dengan Idempotency-Key ini yang masih diproses. |
quota_exceeded | 402 | Kuota kirim bulanan pada paket Anda sudah habis. |
media_too_large | 413 | Berkas media melebihi batas 20 MB. |
storage_exceeded | 413 | Total penyimpanan media tenant sudah penuh. Hapus sebagian media dulu. |
unsafe_url | 422 | media_url harus tautan https yang bisa diakses publik. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Daftar pesan
GET /wa/messagesMembaca riwayat pesan tenant, terbaru dulu, dengan cursor keyset.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
account | uuid | string (query) | Opsional | ID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890. |
account_id | uuid (query) | Opsional | Diterima juga, memakai nama field milik dashboard untuk akun yang sama. |
direction | in | out (query) | Opsional | in untuk pesan masuk, out untuk pesan keluar. |
status | string (query) | Opsional | Menyaring berdasarkan status: queued, sending, sent, delivered, read, failed, atau received. |
from | RFC3339 (query) | Opsional | Hanya pesan yang dibuat pada atau setelah waktu ini. |
to | RFC3339 (query) | Opsional | Hanya pesan yang dibuat pada atau sebelum waktu ini. |
cursor | string (query) | Opsional | Nilai next_cursor dari halaman sebelumnya. |
limit | number (query) | Opsional | Jumlah data per halaman, dibatasi maksimum di server. |
Contoh permintaan
curl "https://api.whapify.id/api/v1/wa/messages?direction=out&status=delivered&limit=20" \
-H "Authorization: Bearer wh_live_••••"Respons sukses
200
{
"messages": [
{
"id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"campaign_id": null,
"direction": "out",
"wa_message_id": null,
"chat_jid": "[email protected]",
"recipient": "6281234567890",
"group_name": null,
"type": "text",
"body": "Halo dari Whapify",
"media": null,
"location": null,
"status": "queued",
"error": null,
"scheduled_at": null,
"created_at": "2026-09-01T09:12:00Z",
"sent_at": null,
"updated_at": "2026-09-01T09:12:00Z"
},
{
"id": "8d7c6b5a-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"campaign_id": null,
"direction": "in",
"wa_message_id": "3EB0A1B2C3D4E5F60718",
"chat_jid": "[email protected]",
"recipient": "[email protected]",
"group_name": "Support Team",
"type": "text",
"body": "Sudah dikirim ya",
"media": null,
"location": null,
"status": "received",
"error": null,
"scheduled_at": null,
"created_at": "2026-09-01T09:12:00Z",
"sent_at": null,
"updated_at": "2026-09-01T09:12:00Z"
}
],
"next_cursor": "MjAyNi0wOS0wMVQwOToxMjowMFp8M2YyYTFiMGM"
}Baris kedua di atas datang dari sebuah grup. Yang membedakannya cuma chat_jid: kalau berakhiran @g.us, chat itu grup, dan recipient berisi peserta yang menulis pesannya, bukan grupnya. group_name adalah nama grup menurut direktori grup, dan bernilai null baik untuk chat pribadi maupun untuk grup yang belum pernah dibaca dari perangkat. Nilainya tidak pernah diisi dengan ID grup, karena ID bukan nama.
Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Validasi nomor
POST /wa/validateMemeriksa apakah nomor terdaftar di WhatsApp. Nomor yang tidak bisa dibaca sebagai nomor telepon dilaporkan sebagai tidak terdaftar, bukan menggagalkan seluruh batch.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
account | uuid | string | Wajib | ID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890. |
phones[] | string[] | Wajib | Sampai 50 nomor, format E.164 tanpa tanda plus. Nomor yang tidak bisa dibaca sebagai nomor telepon dilaporkan sebagai tidak terdaftar, bukan menggagalkan seluruh batch. |
Contoh permintaan
curl -X POST https://api.whapify.id/api/v1/wa/validate \
-H "Authorization: Bearer wh_live_••••" \
-H "Content-Type: application/json" \
-d '{
"account": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"phones": ["081200000123", "6281234567891", "0812-BAD-NUMBER"]
}'Respons sukses
200
{
"results": [
{ "phone": "6281200000123", "exists": true, "jid": "[email protected]" },
{ "phone": "6281234567891", "exists": false, "jid": "" },
{ "phone": "0812-BAD-NUMBER", "exists": false, "jid": "" }
]
}Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
account_not_connected | 409 | Akun WhatsApp yang dipakai mengirim sedang tidak terhubung. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Daftar grup
GET /wa/groupsMengambil daftar grup yang terlihat oleh sebuah akun.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
account | uuid | string (query) | Wajib | ID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890. |
Contoh permintaan
curl "https://api.whapify.id/api/v1/wa/groups?account=9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d" \
-H "Authorization: Bearer wh_live_••••"Respons sukses
200
{
"groups": [
{
"id": "0c1b2a39-4859-4677-9685-9a4b3c2d1e0f",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"gid": "[email protected]",
"name": "Support Team",
"participants": 12,
"cached_at": "2026-09-01T09:12:00Z"
}
]
}Error
| Kode | Status | Keterangan |
|---|---|---|
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Mulai campaign
POST /wa/campaign/startMembuat dan langsung memulai campaign dalam satu panggilan, ke daftar penerima atau grup kontak. Campaign yang gagal dimulai akan dihapus lagi, bukan ditinggal sebagai draf yang tidak diinginkan.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
account | uuid | string | Wajib | ID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890. |
name | string | Wajib | Label untuk campaign, atau nama untuk kontak. |
recipients[] | string[] | Opsional | Daftar tujuan secara eksplisit, nomor telepon saja. Pakai ini atau group_id, tidak keduanya. |
group_id | uuid | Opsional | Mengirim ke semua anggota grup kontak ini, bukan daftar penerima. Ini adalah ID grup kontak, bukan ID grup WhatsApp. |
message | string | Wajib | Isi teks. Opsional untuk pesan location. |
throttle | number (ms) | Opsional | Jeda dalam milidetik antar pengiriman pada campaign, dibatasi 500-60000. Satu angka ini menjadi batas bawah sekaligus batas atas rentang jeda. |
Contoh permintaan
curl -X POST https://api.whapify.id/api/v1/wa/campaign/start \
-H "Authorization: Bearer wh_live_••••" \
-H "Content-Type: application/json" \
-d '{
"account": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"name": "Promo September",
"recipients": ["6281234567890", "6281234567891"],
"message": "Promo spesial bulan ini",
"throttle": 2000
}'Respons sukses
202
{
"id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a49382104",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"name": "Promo September",
"status": "running",
"type": "text",
"body": "Promo spesial bulan ini",
"media_id": null,
"throttle_min_ms": 2000,
"throttle_max_ms": 2000,
"total": 240,
"queued": 240,
"sent": 0,
"delivered": 0,
"failed": 0,
"started_at": "2026-09-01T09:12:00Z",
"finished_at": null,
"created_at": "2026-09-01T09:12:00Z",
"updated_at": "2026-09-01T09:12:00Z"
}Kampanye hanya menerima nomor telepon. ID grup ditolak dengan validation_failed, jadi setiap phone di daftar item selalu berupa nomor. Untuk mengirim ke grup, pakai POST /wa/send dengan ID grup sebagai recipient.
Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
invalid_state | 409 | Campaign sedang tidak dalam status yang mengizinkan aksi ini. |
account_not_connected | 409 | Akun WhatsApp yang dipakai mengirim sedang tidak terhubung. |
quota_exceeded | 402 | Kuota kirim bulanan pada paket Anda sudah habis. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Hentikan campaign
POST /wa/campaign/stopMenghentikan campaign yang sedang berjalan atau dijeda. Menghentikan campaign yang sudah selesai atau sudah dihentikan dijawab dengan invalid_state.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
id | uuid | Wajib | ID campaign, dari respons saat memulai. |
Contoh permintaan
curl -X POST https://api.whapify.id/api/v1/wa/campaign/stop \
-H "Authorization: Bearer wh_live_••••" \
-H "Content-Type: application/json" \
-d '{
"id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a49382104"
}'Respons sukses
200
{
"id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a49382104",
"account_id": "9f1c0f8e-3b4a-4a2e-9c5d-6e7f8a9b0c1d",
"name": "Promo September",
"status": "stopped",
"type": "text",
"body": "Promo spesial bulan ini",
"media_id": null,
"throttle_min_ms": 2000,
"throttle_max_ms": 2000,
"total": 240,
"queued": 0,
"sent": 187,
"delivered": 0,
"failed": 53,
"started_at": "2026-09-01T09:12:00Z",
"finished_at": "2026-09-01T09:44:12Z",
"created_at": "2026-09-01T09:12:00Z",
"updated_at": "2026-09-01T09:12:00Z"
}Error
| Kode | Status | Keterangan |
|---|---|---|
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
invalid_state | 409 | Campaign sedang tidak dalam status yang mengizinkan aksi ini. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Daftar kontak
GET /wa/contactsMembaca daftar kontak tenant, bisa disaring berdasarkan nama, nomor, atau grup kontak, dengan cursor keyset.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
search | string (query) | Opsional | Menyaring berdasarkan nama atau nomor, cocok di bagian mana pun dari nilainya. |
group_id | uuid (query) | Opsional | Hanya kontak yang termasuk grup kontak ini. |
cursor | string (query) | Opsional | Nilai next_cursor dari halaman sebelumnya. |
limit | number (query) | Opsional | Jumlah data per halaman, dibatasi maksimum di server. |
Contoh permintaan
curl "https://api.whapify.id/api/v1/wa/contacts?search=budi&limit=20" \
-H "Authorization: Bearer wh_live_••••"Respons sukses
200
{
"contacts": [
{
"id": "c3530767-8f0a-4b88-8df4-b07d423f9f7a",
"name": "Budi",
"phone": "6281234567890",
"tags": [],
"notes": null,
"groups": [],
"created_at": "2026-09-01T09:12:00Z",
"updated_at": "2026-09-01T09:12:00Z"
}
],
"next_cursor": ""
}Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Buat kontak
POST /wa/contactsMenambah satu kontak. Nomor yang sudah dimiliki tenant akan gagal dengan already_exists.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
name | string | Wajib | Label untuk campaign, atau nama untuk kontak. |
phone | string | Wajib | Nomor WhatsApp Indonesia dalam format apa pun yang umum. Disimpan ternormalisasi jadi angka saja, diawali 62. |
tags[] | string[] | Opsional | Label bebas untuk kontak ini. |
notes | string | Opsional | Catatan bebas tentang kontak ini. |
Contoh permintaan
curl -X POST https://api.whapify.id/api/v1/wa/contacts \
-H "Authorization: Bearer wh_live_••••" \
-H "Content-Type: application/json" \
-d '{
"name": "Budi",
"phone": "0812-3456-7890",
"tags": ["vip"]
}'Respons sukses
201
{
"id": "c3530767-8f0a-4b88-8df4-b07d423f9f7a",
"name": "Budi",
"phone": "6281234567890",
"tags": ["vip"],
"notes": null,
"groups": [],
"created_at": "2026-09-01T09:12:00Z",
"updated_at": "2026-09-01T09:12:00Z"
}Error
| Kode | Status | Keterangan |
|---|---|---|
validation_failed | 422 | Salah satu field pada body tidak lolos validasi. |
invalid_json | 400 | Body bukan JSON yang valid, atau memuat field yang tidak dikenal. |
unauthorized | 401 | Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa. |
forbidden | 403 | API key ini tidak punya izin yang dibutuhkan endpoint ini. |
account_suspended | 403 | Tenant pemilik API key ini sedang disuspend. |
already_exists | 409 | Data dengan nilai ini sudah ada. |
rate_limited | 429 | Terlalu banyak permintaan dari klien ini dalam satu menit. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
Ambil media
GET /media/{id}Membaca isi lampiran sebuah pesan lewat tautan bertanda tangan. Tidak memakai header Authorization.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
exp | number (query) | Wajib | Waktu kedaluwarsa tautan, dari media block sebuah pesan. |
sig | string (query) | Wajib | Tanda tangan tautan, dari media block sebuah pesan. |
Contoh permintaan
curl "https://api.whapify.id/api/v1/media/7c6b5a49-3821-4d0e-9f1a-2b3c4d5e6f70?exp=1798000325&sig=5f2b1c9e" \
--output katalog.pngRespons sukses
200
Content-Type: image/png
Content-Disposition: inline; filename="7c6b5a49-3821-4d0e-9f1a-2b3c4d5e6f70.png"
X-Content-Type-Options: nosniff
Cache-Control: no-storeError
| Kode | Status | Keterangan |
|---|---|---|
not_found | 404 | Data yang diminta bukan milik Anda, atau memang tidak ada. |
invalid_signature | 401 | Tanda tangan pada tautan media tidak cocok. |
link_expired | 410 | Tautan media sudah lewat masa berlaku. Baca ulang pesannya untuk dapat tautan baru. |
internal | 500 | Kesalahan di sisi server. Coba lagi. |
POST /wa/send dalam Node dan PHP
Permintaan yang sama seperti contoh curl di atas, dalam dua bahasa lagi.
const res = await fetch("https://api.whapify.id/api/v1/wa/send", {
method: "POST",
headers: {
Authorization: "Bearer wh_live_••••",
"Content-Type": "application/json",
},
body: JSON.stringify({
account: "6281234500000",
recipient: "6281234567890",
type: "text",
body: "Halo dari Whapify",
}),
});
console.log(await res.json()); // 202: { id, status: "queued", ... }Webhook
Daftarkan URL per jenis event dari halaman Webhooks di dashboard. Whapify mengirim envelope di bawah ini ke sana setiap kali event yang didaftarkan terjadi, ditandatangani dengan secret yang ditampilkan sekali saat webhook dibuat.
Event
message.received: Pesan WhatsApp masuk pada akun yang tertaut.message.status: Status pesan terkirim berubah: sent, delivered, read, atau failed.account.status: Status koneksi akun yang tertaut berubah.campaign.progress: Jumlah pada campaign yang berjalan berubah.
Envelope
Setiap pengiriman adalah POST dengan body berikut:
{
"id": "evt_01JA4C6E8G0K2M4P6R8T0V2X4Z",
"type": "message.status",
"created_at": "2026-09-01T09:12:05Z",
"data": {
"id": "msg_01J8X9Q7Z3K2N4P6R8T0V2W4Y6",
"status": "delivered"
}
}Header
| Kode | Keterangan |
|---|---|
X-Signature | HMAC-SHA256 heksadesimal dari body mentah, memakai secret endpoint. |
X-Timestamp | Waktu pengiriman, sebagai Unix timestamp dalam detik. |
X-Event-Id | ID tetap untuk pengiriman ini. Pakai untuk membuang duplikat dari percobaan ulang. |
Contoh pengiriman
Yang Whapify kirim ke endpoint Anda, bukan yang Anda kirim ke Whapify.
curl -X POST https://your-app.example.com/webhooks/whapify \
-H "Content-Type: application/json" \
-H "X-Signature: sha256=5f2b1c9e8a3d4f6b7c8e9a0d1f2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b" \
-H "X-Timestamp: 1798000325" \
-H "X-Event-Id: evt_01JA4C6E8G0K2M4P6R8T0V2X4Z" \
-d '{ "id": "evt_01JA4C6E8G0K2M4P6R8T0V2X4Z", "type": "message.status", "created_at": "2026-09-01T09:12:05Z", "data": { "id": "msg_01J8X9Q7Z3K2N4P6R8T0V2W4Y6", "status": "delivered" } }'Percobaan ulang
Pengiriman yang tidak dijawab dengan status 2xx dicoba ulang sampai 5 kali, dengan jeda yang makin panjang antar percobaan.
Memverifikasi pengiriman
Hitung ulang signature dari body mentah dan bandingkan dengan X-Signature secara constant-time, seperti contoh di bawah ini.
import { createHmac, timingSafeEqual } from "node:crypto";
// Every delivery carries the X-Signature header: "sha256=" plus the hex
// HMAC-SHA256 of the raw body, keyed with the endpoint secret.
export function verify(rawBody, signature, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signature ?? "", "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}