Lewati ke konten
Developer

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 sama

Key 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.

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
already_exists409Data dengan nilai ini sudah ada.
invalid_state409Campaign sedang tidak dalam status yang mengizinkan aksi ini.
account_not_connected409Akun WhatsApp yang dipakai mengirim sedang tidak terhubung.
idempotency_in_progress409Ada permintaan lain dengan Idempotency-Key ini yang masih diproses.
quota_exceeded402Kuota kirim bulanan pada paket Anda sudah habis.
media_too_large413Berkas media melebihi batas 20 MB.
storage_exceeded413Total penyimpanan media tenant sudah penuh. Hapus sebagian media dulu.
unsafe_url422media_url harus tautan https yang bisa diakses publik.
invalid_signature401Tanda tangan pada tautan media tidak cocok.
link_expired410Tautan media sudah lewat masa berlaku. Baca ulang pesannya untuk dapat tautan baru.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan 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

KodeKeterangan
X-RateLimit-LimitJumlah permintaan yang diperbolehkan pada jendela waktu berjalan.
X-RateLimit-RemainingSisa permintaan pada jendela waktu berjalan.
Retry-AfterJumlah 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.

WhatsApp

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/send

Menerima satu pesan teks, media, atau lokasi dan mengantrekannya. Jawabannya 202: pesan diterima, belum terkirim.

FieldTipeWajibKeterangan
accountuuid | stringWajibID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890.
account_iduuidOpsionalDiterima juga, memakai nama field milik dashboard untuk akun yang sama.
recipientstringWajibTujuan: nomor dalam format E.164 tanpa tanda plus, atau ID grup.
typetext | image | video | audio | document | locationWajibtext, image, video, audio, document, atau location.
bodystringOpsionalIsi teks. Opsional untuk pesan location.
media_iduuidOpsionalID media dari berkas yang sudah diunggah lewat media library di dashboard. Pakai ini atau media_url, tidak keduanya.
media_urlstringOpsionalURL https publik ke media. Dipakai jika tidak mengirim media_id.
location.latnumberOpsionalLintang, berupa angka.
location.lngnumberOpsionalBujur, berupa angka.
location.namestringOpsionalNama 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
account_not_connected409Akun WhatsApp yang dipakai mengirim sedang tidak terhubung.
idempotency_in_progress409Ada permintaan lain dengan Idempotency-Key ini yang masih diproses.
quota_exceeded402Kuota kirim bulanan pada paket Anda sudah habis.
media_too_large413Berkas media melebihi batas 20 MB.
storage_exceeded413Total penyimpanan media tenant sudah penuh. Hapus sebagian media dulu.
unsafe_url422media_url harus tautan https yang bisa diakses publik.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Daftar pesan

GET /wa/messages

Membaca riwayat pesan tenant, terbaru dulu, dengan cursor keyset.

FieldTipeWajibKeterangan
accountuuid | string (query)OpsionalID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890.
account_iduuid (query)OpsionalDiterima juga, memakai nama field milik dashboard untuk akun yang sama.
directionin | out (query)Opsionalin untuk pesan masuk, out untuk pesan keluar.
statusstring (query)OpsionalMenyaring berdasarkan status: queued, sending, sent, delivered, read, failed, atau received.
fromRFC3339 (query)OpsionalHanya pesan yang dibuat pada atau setelah waktu ini.
toRFC3339 (query)OpsionalHanya pesan yang dibuat pada atau sebelum waktu ini.
cursorstring (query)OpsionalNilai next_cursor dari halaman sebelumnya.
limitnumber (query)OpsionalJumlah 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Validasi nomor

POST /wa/validate

Memeriksa apakah nomor terdaftar di WhatsApp. Nomor yang tidak bisa dibaca sebagai nomor telepon dilaporkan sebagai tidak terdaftar, bukan menggagalkan seluruh batch.

FieldTipeWajibKeterangan
accountuuid | stringWajibID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890.
phones[]string[]WajibSampai 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
account_not_connected409Akun WhatsApp yang dipakai mengirim sedang tidak terhubung.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Daftar grup

GET /wa/groups

Mengambil daftar grup yang terlihat oleh sebuah akun.

FieldTipeWajibKeterangan
accountuuid | string (query)WajibID 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

KodeStatusKeterangan
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Mulai campaign

POST /wa/campaign/start

Membuat 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.

FieldTipeWajibKeterangan
accountuuid | stringWajibID akun Anda, atau nomor akun tersebut dalam format apa pun yang umum: 081234567890, +62 812-3456-7890, atau 6281234567890.
namestringWajibLabel untuk campaign, atau nama untuk kontak.
recipients[]string[]OpsionalDaftar tujuan secara eksplisit, nomor telepon saja. Pakai ini atau group_id, tidak keduanya.
group_iduuidOpsionalMengirim ke semua anggota grup kontak ini, bukan daftar penerima. Ini adalah ID grup kontak, bukan ID grup WhatsApp.
messagestringWajibIsi teks. Opsional untuk pesan location.
throttlenumber (ms)OpsionalJeda 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
invalid_state409Campaign sedang tidak dalam status yang mengizinkan aksi ini.
account_not_connected409Akun WhatsApp yang dipakai mengirim sedang tidak terhubung.
quota_exceeded402Kuota kirim bulanan pada paket Anda sudah habis.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Hentikan campaign

POST /wa/campaign/stop

Menghentikan campaign yang sedang berjalan atau dijeda. Menghentikan campaign yang sudah selesai atau sudah dihentikan dijawab dengan invalid_state.

FieldTipeWajibKeterangan
iduuidWajibID 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

KodeStatusKeterangan
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
invalid_state409Campaign sedang tidak dalam status yang mengizinkan aksi ini.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Daftar kontak

GET /wa/contacts

Membaca daftar kontak tenant, bisa disaring berdasarkan nama, nomor, atau grup kontak, dengan cursor keyset.

FieldTipeWajibKeterangan
searchstring (query)OpsionalMenyaring berdasarkan nama atau nomor, cocok di bagian mana pun dari nilainya.
group_iduuid (query)OpsionalHanya kontak yang termasuk grup kontak ini.
cursorstring (query)OpsionalNilai next_cursor dari halaman sebelumnya.
limitnumber (query)OpsionalJumlah 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Buat kontak

POST /wa/contacts

Menambah satu kontak. Nomor yang sudah dimiliki tenant akan gagal dengan already_exists.

FieldTipeWajibKeterangan
namestringWajibLabel untuk campaign, atau nama untuk kontak.
phonestringWajibNomor WhatsApp Indonesia dalam format apa pun yang umum. Disimpan ternormalisasi jadi angka saja, diawali 62.
tags[]string[]OpsionalLabel bebas untuk kontak ini.
notesstringOpsionalCatatan 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

KodeStatusKeterangan
validation_failed422Salah satu field pada body tidak lolos validasi.
invalid_json400Body bukan JSON yang valid, atau memuat field yang tidak dikenal.
unauthorized401Header Authorization tidak ada, atau API key-nya tidak dikenal, sudah dicabut, atau sudah kedaluwarsa.
forbidden403API key ini tidak punya izin yang dibutuhkan endpoint ini.
account_suspended403Tenant pemilik API key ini sedang disuspend.
already_exists409Data dengan nilai ini sudah ada.
rate_limited429Terlalu banyak permintaan dari klien ini dalam satu menit.
internal500Kesalahan di sisi server. Coba lagi.

Ambil media

GET /media/{id}

Membaca isi lampiran sebuah pesan lewat tautan bertanda tangan. Tidak memakai header Authorization.

FieldTipeWajibKeterangan
expnumber (query)WajibWaktu kedaluwarsa tautan, dari media block sebuah pesan.
sigstring (query)WajibTanda 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.png

Respons sukses

200
Content-Type: image/png
Content-Disposition: inline; filename="7c6b5a49-3821-4d0e-9f1a-2b3c4d5e6f70.png"
X-Content-Type-Options: nosniff
Cache-Control: no-store

Error

KodeStatusKeterangan
not_found404Data yang diminta bukan milik Anda, atau memang tidak ada.
invalid_signature401Tanda tangan pada tautan media tidak cocok.
link_expired410Tautan media sudah lewat masa berlaku. Baca ulang pesannya untuk dapat tautan baru.
internal500Kesalahan 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

KodeKeterangan
X-SignatureHMAC-SHA256 heksadesimal dari body mentah, memakai secret endpoint.
X-TimestampWaktu pengiriman, sebagai Unix timestamp dalam detik.
X-Event-IdID 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);
}