WhatsApp punya kategori template khusus Authentication yang memang didesain buat One-Time Password (OTP) dan kode verifikasi.
Biar konversi naik dan user lebih nyaman, kamu bisa tambah tombol Copy Code. User tinggal tap sekali, kode langsung kesalin ke clipboard.
Aturan Meta buat template OTP
Meta ketat soal format dan keamanan template authentication:
- Nggak boleh pakai media: Nggak bisa tambah gambar, video, atau lampiran dokumen.
- Nggak boleh ada link eksternal: Template nggak boleh punya tombol URL atau nomor HP custom.
- Tombol Copy Code: Harus kamu set sebagai tombol tipe
copy_codedengan parameter OTP dinamis yang terikat ke tombol tersebut.
Mapping parameter dinamis
Pas kirim template OTP dengan tombol Copy Code, kamu harus isi parameter buat body pesan dan buat tombolnya.
Misal template kamu diisi:
- Body:
"{{1}} adalah kode verifikasi kamu. Jangan bagikan kode ini demi keamanan." - Button: Copy Code
Request API kamu harus kasih:
- Parameter body: kode-nya (mis.
582910). - Parameter button: kode yang sama biar bisa di-copy lewat tombol (mis.
582910).
Contoh kirim kode
Pakai pola request ini buat kirim kode OTP:
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": {
"name": "otp_verification",
"language": {
"code": "en_US"
},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "582910"
}
]
},
{
"type": "button",
"sub_type": "copy_code",
"index": 0,
"parameters": [
{
"type": "text",
"text": "582910"
}
]
}
]
}
}'import { WazapinClient } from "@wazapin/sdk";
const wazapin = new WazapinClient({ apiKey: process.env.WAZAPIN_API_KEY! });
await wazapin.messages.send({
channel_id: "wzp_abc123",
to: "6281234567890",
type: "template",
content: {
name: "otp_verification",
language: { code: "en_US" },
components: [
{
type: "body",
parameters: [{ type: "text", text: "582910" }]
},
{
type: "button",
sub_type: "copy_code",
index: 0,
parameters: [{ type: "text", text: "582910" }]
}
]
}
});import requests
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": {
"name": "otp_verification",
"language": {"code": "en_US"},
"components": [
{
"type": "body",
"parameters": [{"type": "text", "text": "582910"}]
},
{
"type": "button",
"sub_type": "copy_code",
"index": 0,
"parameters": [{"type": "text", "text": "582910"}]
}
]
}
},
timeout=30
).raise_for_status()package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"channel_id": "wzp_abc123",
"to": "6281234567890",
"type": "template",
"content": {
"name": "otp_verification",
"language": {
"code": "en_US"
},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "582910"
}
]
},
{
"type": "button",
"sub_type": "copy_code",
"index": 0,
"parameters": [
{
"type": "text",
"text": "582910"
}
]
}
]
}
}`)
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": {
"name": "otp_verification",
"language": {
"code": "en_US"
},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "582910"
}
]
},
{
"type": "button",
"sub_type": "copy_code",
"index": 0,
"parameters": [
{
"type": "text",
"text": "582910"
}
]
}
]
}
}';
$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": {"name": "otp_verification", "language": {"code": "en_US"}, "components": [{"type": "body", "parameters": [{"type": "text", "text": "582910"}]}, {"type": "button", "sub_type": "copy_code", "index": 0, "parameters": [{"type": "text", "text": "582910"}]}]}}
""";
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": {"name": "otp_verification", "language": {"code": "en_US"}, "components": [{"type": "body", "parameters": [{"type": "text", "text": "582910"}]}, {"type": "button", "sub_type": "copy_code", "index": 0, "parameters": [{"type": "text", "text": "582910"}]}]}}'
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\": {\"name\": \"otp_verification\", \"language\": {\"code\": \"en_US\"}, \"components\": [{\"type\": \"body\", \"parameters\": [{\"type\": \"text\", \"text\": \"582910\"}]}, {\"type\": \"button\", \"sub_type\": \"copy_code\", \"index\": 0, \"parameters\": [{\"type\": \"text\", \"text\": \"582910\"}]}]}}";
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());Troubleshooting
Tombol nggak mau copy kode
Pastiin index cocok sama urutan tombol Copy Code di konfigurasi template kamu (biasanya 0 kalau cuma ada satu tombol). Dan sub_type harus persis "copy_code".
Template kena reject
Kalau Meta nolak template pas kamu bikin, cek lagi — jangan selipin link, media, atau teks yang kesannya promo di body atau tombolnya.