Pakai template kalau kamu yang mau nge-chat duluan — update pesanan, kode OTP, pengingat janji — apalagi kalau udah lewat 24 jam sejak chat terakhir pelanggan. Semua template harus di-approve Meta dulu ya.
Nge-kirim template butuh lebih dari sekadar nama dan bahasa. Kamu harus ngisi components yang sesuai sama struktur template-nya: variabel body, header (bisa gambar atau teks), dan parameter tombol kalau template-nya punya tombol dengan link dinamis.
Sebelum ngirim
Bikin dan tunggu template di-approve
Bikin template di aplikasi Wazapin atau sync dari Meta kalau pakai channel official. Pastikan statusnya udah approved dulu baru bisa dipakai.
Cek daftar template kamu pakai GET /v1/templates. Cek juga dukungan channel buat bedanya perilaku official vs unofficial.
Cek struktur template-nya
Buka template di dashboard dan catat bagian-bagiannya:
- Header — bisa kosong, teks (
{{1}}), gambar, video, atau dokumen - Body — teks dengan placeholder
{{1}},{{2}}, … - Tombol — quick reply, URL (bisa ada
{{1}}di link-nya), copy code, dll.
Array components yang kamu kirim harus ngikutin urutan bagian ini persis.
Susun array components
Tiap bagian jadi satu object di content.template.components:
type di components |
Kapan dipakai |
|---|---|
header |
Kalau template punya header dinamis (teks, gambar, video, dokumen) |
body |
Kalau body template ada variabel {{n}} |
button |
Kalau ada tombol URL atau tombol dinamis lain (sub_type, index) |
Nggak ada variabel di bagian itu? Skip aja — nggak perlu dikirim.
Format request
Semua kirim template pakai POST /v1/messages dengan type: "template" dan object content.template di dalamnya (formatnya kompatibel sama Meta).
| Field | Wajib? | Deskripsi |
|---|---|---|
channel_id |
Ya | Channel WhatsApp yang udah terhubung |
to |
Ya | Nomor tujuan format internasional |
type |
Ya | template |
content.template.name |
Ya | Nama template yang udah di-approve |
content.template.language.code |
Ya | Kode bahasa, mis. en_US, id |
content.template.components |
Sering iya | Parameter buat header / body / button |
Contoh: cuma variabel body
Isi body template: Hi {{1}}, your order {{2}} is on the way.
curl -X POST "https://api.wazapin.com/v1/messages" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "order_shipped",
"language": { "code": "en_US" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "John" },
{ "type": "text", "text": "ORD-42" }
]
}
]
}
}
}'import { WazapinClient, templateMessage } from "@wazapin/sdk";
const wazapin = new WazapinClient({ apiKey: process.env.WAZAPIN_API_KEY! });
const { data: message } = await wazapin.messages.send(
templateMessage({
channel_id: "wzp_abc123",
to: "6281234567890",
name: "order_shipped",
language: "en_US",
components: [
{
type: "body",
parameters: [
{ type: "text", text: "John" },
{ type: "text", text: "ORD-42" },
],
},
],
}),
);
console.log(message.id);import requests
response = requests.post(
"https://api.wazapin.com/v1/messages",
headers={"X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json"},
json={
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "order_shipped",
"language": {"code": "en_US"},
"components": [
{
"type": "body",
"parameters": [
{"type": "text", "text": "John"},
{"type": "text", "text": "ORD-42"},
],
}
],
}
},
},
timeout=30,
)
response.raise_for_status()
print(response.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "order_shipped",
"language": {
"code": "en_US"
},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "John"
},
{
"type": "text",
"text": "ORD-42"
}
]
}
]
}
}
}`)
req, err := http.NewRequest(http.MethodPost, "https://api.wazapin.com/v1/messages", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("X-Api-Key", os.Getenv("WAZAPIN_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 201 = message accepted; delivery status via webhooks / GET /v1/messages/{id}
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}<?php
$payload = '{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "order_shipped",
"language": {
"code": "en_US"
},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "John"
},
{
"type": "text",
"text": "ORD-42"
}
]
}
]
}
}
}';
$ch = curl_init("https://api.wazapin.com/v1/messages");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"X-Api-Key: " . getenv("WAZAPIN_API_KEY"),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 201 = message accepted; delivery status via webhooks / GET /v1/messages/{id}
echo $httpCode . "\n" . $response . "\n";import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class SendMessage {
public static void main(String[] args) throws Exception {
String body = """
{"channel_id": "wzp_abc123", "to": "6281234567890", "type": "template", "content": {"template": {"name": "order_shipped", "language": {"code": "en_US"}, "components": [{"type": "body", "parameters": [{"type": "text", "text": "John"}, {"type": "text", "text": "ORD-42"}]}]}}}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.wazapin.com/v1/messages"))
.header("X-Api-Key", System.getenv("WAZAPIN_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// 201 = message accepted; delivery status via webhooks / GET /v1/messages/{id}
System.out.println(response.statusCode());
System.out.println(response.body());
}
}require "net/http"
require "uri"
uri = URI("https://api.wazapin.com/v1/messages")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["X-Api-Key"] = ENV["WAZAPIN_API_KEY"]
request.body = '{"channel_id": "wzp_abc123", "to": "6281234567890", "type": "template", "content": {"template": {"name": "order_shipped", "language": {"code": "en_US"}, "components": [{"type": "body", "parameters": [{"type": "text", "text": "John"}, {"type": "text", "text": "ORD-42"}]}]}}}'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
# 201 = message accepted; delivery status via webhooks / GET /v1/messages/{id}
puts response.code
puts response.bodyusing System.Net.Http;
using System.Text;
var body = "{\"channel_id\": \"wzp_abc123\", \"to\": \"6281234567890\", \"type\": \"template\", \"content\": {\"template\": {\"name\": \"order_shipped\", \"language\": {\"code\": \"en_US\"}, \"components\": [{\"type\": \"body\", \"parameters\": [{\"type\": \"text\", \"text\": \"John\"}, {\"type\": \"text\", \"text\": \"ORD-42\"}]}]}}}";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", Environment.GetEnvironmentVariable("WAZAPIN_API_KEY"));
var response = await client.PostAsync(
"https://api.wazapin.com/v1/messages",
new StringContent(body, Encoding.UTF8, "application/json"));
// 201 = message accepted; delivery status via webhooks / GET /v1/messages/{id}
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());Contoh: header gambar + body
Template dengan header gambar dan satu variabel body.
{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "promo_with_image",
"language": { "code": "id" },
"components": [
{
"type": "header",
"parameters": [
{
"type": "image",
"image": { "link": "https://cdn.example.com/promo.jpg" }
}
]
},
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Ramadan Sale" }
]
}
]
}
}
}Contoh: header teks dinamis
Header template-nya: Hello {{1}} — tinggal kirim header component di dalam content.template.components:
{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "greeting_header",
"language": { "code": "en_US" },
"components": [
{
"type": "header",
"parameters": [
{ "type": "text", "text": "John" }
]
},
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Your appointment is confirmed." }
]
}
]
}
}
}Contoh: tombol URL dinamis
Template dengan tombol URL yang ada {{1}} di link-nya — component button tinggal taruh di array components yang sama:
{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"template": {
"name": "order_tracking",
"language": { "code": "en_US" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "John" },
{ "type": "text", "text": "ORD-42" }
]
},
{
"type": "button",
"sub_type": "url",
"index": "0",
"parameters": [
{ "type": "text", "text": "ORD-42" }
]
}
]
}
}
}index itu urutan tombol di template (mulai dari 0). sub_type harus sama kayak yang di-approve Meta — url, quick_reply, copy_code, dll.
Mau lihat daftar template atau sync dari Meta? Pakai wazapin.templates.list() dan wazapin.templates.sync(). Detail lengkapnya di SDK template.
Kesalahan yang sering kejadian
| Kejadiannya | Beresinnya gimana |
|---|---|
| Template belum di-approve | Tunggu Meta approve dulu, atau pakai template lain yang udah approved |
Salah language.code |
Harus persis sama kayak locale template-nya (en_US bukan en) |
components kurang |
Isi semua {{n}} yang ada di header / body / tombol — jangan ada yang kelewat |
Pakai template_name datar di content |
Harus pakai content.template.name yang nested |
| URL gambar header bukan HTTPS | Pakai URL HTTPS publik yang bisa diakses Meta |
Terkait
- Gambaran cara ngirim — kapan pakai sesi vs template
- Contoh: kirim template
- Kirim pesan — referensi API + playground
- Dukungan channel — bedanya perilaku kalau pakai channel unofficial
Endpoint
POST https://api.wazapin.com/v1/messages
Authenticate with X-Api-Key. See Authentication.
Response
On success, the API returns 201 Created with a lean accept-response (status often starts as queued).
{
"id": "9f1fd66d-c37a-4b50-a8c2-b4dca523f9c8",
"status": "queued",
"channel_id": "wzp_abc123",
"to": "6281234567890",
"created_at": "2026-03-04T06:20:10Z"
}Track delivery with Webhooks or GET /v1/messages/{messageID} (full record, including provider_message_id once the provider acknowledges it).