Langsung ke isigocap

Dokumentasi API

Memverifikasi tanda tangan webhook (bagian 4.6)

Tolak permintaan yang tidak berasal dari gocap.

Langkah

  1. Baca badan permintaan mentah sebelum di-parse

    Simpan byte badan persis seperti diterima. Framework yang mem-parse JSON otomatis perlu diatur agar badan mentahnya tetap tersedia.

  2. Hitung HMAC-SHA256 atas X-Gocap-Timestamp, satu titik, lalu badan mentah

    Kuncinya adalah Webhook Secret dari Pengaturan akun persis seperti ditampilkan: 64 karakter heksa itu sendiri adalah kuncinya.

  3. Bandingkan sha256= diikuti hasilnya dengan header X-Gocap-Signature secara waktu-konstan

    Pakai hash_equals, crypto.timingSafeEqual, atau hmac.compare_digest, bukan ==.

  4. Tolak bila X-Gocap-Timestamp bukan angka atau selisihnya dengan jam server Anda lebih dari 300 detik

  5. Balas 2xx tanpa badan, lalu proses isinya

    gocap menunggu balasan paling lama 10 detik. Badan JSON berisi message atau url menjadi balasan webhook: gocap mengirimnya sebagai pesan WhatsApp ke pengirim (ke grup bila pesan berasal dari grup) dan memakai kuota. Balas 401 untuk permintaan yang ditolak.

Contoh penerima

Setiap contoh berisi fungsi verifikasi yang lulus semua vektor uji dan server kecil yang memakainya.

PHP
<?php
// Webhook Secret dari Pengaturan akun, disalin apa adanya.
$secret = getenv('WAVA_WEBHOOK_SECRET');
if ($secret === false || $secret === '') {
    error_log('WAVA_WEBHOOK_SECRET kosong');
    http_response_code(500);
    exit;
}

// true bila permintaan sah dari gocap. $raw: badan mentah.
// $timestamp, $signature: isi header X-Gocap-Timestamp dan
// X-Gocap-Signature. $secret: Webhook Secret apa adanya.
// $now: detik unix saat ini.
function verifikasi_webhook(
    string $raw,
    string $timestamp,
    string $signature,
    string $secret,
    int $now
): bool {
    if (!ctype_digit($timestamp)) {
        return false;
    }
    if (abs($now - (int) $timestamp) > 300) {
        return false;
    }
    $expected = 'sha256=' . hash_hmac(
        'sha256',
        $timestamp . '.' . $raw,
        $secret
    );
    return hash_equals($expected, $signature);
}

$raw = file_get_contents('php://input');
$sah = verifikasi_webhook(
    $raw,
    $_SERVER['HTTP_X_GOCAP_TIMESTAMP'] ?? '',
    $_SERVER['HTTP_X_GOCAP_SIGNATURE'] ?? '',
    $secret,
    time()
);
if (!$sah) {
    http_response_code(401);
    exit;
}
// Balas 2xx tanpa badan dalam 10 detik, lalu proses.
http_response_code(200);
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}
$event = json_decode($raw, true);
error_log('pesan masuk ' . ($event['inboxid'] ?? ''));
Node.js
const crypto = require("node:crypto");
const http = require("node:http");

// Webhook Secret dari Pengaturan akun, disalin apa adanya.
const secret = process.env.WAVA_WEBHOOK_SECRET;
if (!secret) throw new Error("WAVA_WEBHOOK_SECRET kosong");

// true bila permintaan sah dari gocap. raw: Buffer badan
// mentah. timestamp, signature: isi header
// X-Gocap-Timestamp dan X-Gocap-Signature. secret: Webhook
// Secret apa adanya. now: detik unix saat ini.
function verifikasiWebhook(
  raw,
  timestamp,
  signature,
  secret,
  now,
) {
  if (!/^[0-9]+$/.test(timestamp)) return false;
  if (Math.abs(now - Number(timestamp)) > 300) {
    return false;
  }
  const hmac = crypto.createHmac("sha256", secret);
  hmac.update(timestamp + ".");
  hmac.update(raw);
  const expected = Buffer.from(
    "sha256=" + hmac.digest("hex"),
  );
  const received = Buffer.from(signature);
  // Panjang dibandingkan dalam byte, bukan karakter:
  // header non-ASCII tidak membuat timingSafeEqual crash.
  return (
    received.length === expected.length &&
    crypto.timingSafeEqual(received, expected)
  );
}

http
  .createServer((req, res) => {
    const chunks = [];
    req.on("data", (chunk) => chunks.push(chunk));
    req.on("end", () => {
      const raw = Buffer.concat(chunks);
      const sah = verifikasiWebhook(
        raw,
        String(req.headers["x-gocap-timestamp"] ?? ""),
        String(req.headers["x-gocap-signature"] ?? ""),
        secret,
        Math.floor(Date.now() / 1000),
      );
      if (!sah) {
        res.writeHead(401).end();
        return;
      }
      // Balas 2xx tanpa badan dalam 10 detik, lalu proses.
      res.writeHead(200).end();
      try {
        const event = JSON.parse(raw);
        console.log("pesan masuk", event.inboxid);
      } catch (err) {
        console.error("payload tidak terbaca", err);
      }
    });
  })
  .listen(3000);
Python
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer

# Webhook Secret dari Pengaturan akun, disalin apa adanya.
SECRET = os.environ["WAVA_WEBHOOK_SECRET"]


def verifikasi_webhook(
    raw: bytes,
    timestamp: str,
    signature: str,
    secret: str,
    now: int,
) -> bool:
    """True bila permintaan sah dari gocap.

    raw: badan mentah. timestamp, signature: isi header
    X-Gocap-Timestamp dan X-Gocap-Signature. secret: Webhook
    Secret apa adanya. now: detik unix saat ini.
    """
    if not (timestamp.isascii() and timestamp.isdigit()):
        return False
    if abs(now - int(timestamp)) > 300:
        return False
    mac = hmac.new(
        secret.encode(),
        timestamp.encode() + b"." + raw,
        hashlib.sha256,
    )
    expected = "sha256=" + mac.hexdigest()
    return hmac.compare_digest(
        signature.encode(), expected.encode()
    )


class Penerima(BaseHTTPRequestHandler):
    def do_POST(self):
        panjang = int(self.headers.get("Content-Length", 0))
        raw = self.rfile.read(panjang)
        sah = verifikasi_webhook(
            raw,
            self.headers.get("X-Gocap-Timestamp", ""),
            self.headers.get("X-Gocap-Signature", ""),
            SECRET,
            int(time.time()),
        )
        # Balas 2xx tanpa badan dalam 10 detik, lalu proses.
        self.send_response(200 if sah else 401)
        self.end_headers()
        if sah:
            event = json.loads(raw)
            print("pesan masuk", event.get("inboxid"))


HTTPServer(("", 3000), Penerima).serve_forever()

Simpan Webhook Secret dari Pengaturan akun, disalin apa adanya, di variabel lingkungan WAVA_WEBHOOK_SECRET sebelum menjalankan penerima; jangan menuliskannya di kode.

Kesalahan umum

  • Badan di-parse lalu diserialisasi ulang. JSON yang dibentuk ulang tidak lagi sama byte demi byte dengan yang ditandatangani (spasi, urutan kunci, escape). Hitung HMAC atas badan mentah, lalu parse sesudahnya.
  • Secret didekode dari heksa. Webhook Secret tampil sebagai 64 karakter heksa, dan teks itulah kuncinya. Kunci 32 byte hasil dekode menghasilkan tanda tangan lain, sehingga semua permintaan tertolak.
  • Jam server melenceng. Selisih waktu dihitung dari jam server Anda; jam yang melenceng lebih dari 300 detik menolak permintaan yang sah. Sinkronkan jam dengan NTP.
  • Membalas 401 lalu heran ada percobaan ulang. Setiap balasan selain 2xx dihitung gagal dan dicoba lagi dengan jeda 1, 5, 15, lalu 40 menit antar-percobaan (total 5 percobaan dalam ±61 menit). Itu disengaja: setelah secret di penerima diperbaiki, percobaan berikutnya ditandatangani dengan Webhook Secret yang berlaku saat itu dan diterima.
  • Membandingkan panjang string sebelum timingSafeEqual. Di Node.js, timingSafeEqual melempar galat bila kedua Buffer berbeda panjang byte. Header berkarakter non-ASCII dapat sama panjang dengan tanda tangan asli bila dihitung dalam karakter, tetapi lebih panjang dalam byte; pemeriksaan panjang string lalu lolos dan timingSafeEqual melempar galat. Bandingkan panjang Buffer, seperti pada contoh.

Vektor uji

Uji fungsi verifikasi Anda tanpa menunggu webhook sungguhan. Semua vektor memakai Webhook Secret uji di bawah, bukan secret akun Anda, dan kolom now sebagai jam sekarang. Vektor dibangkitkan tes Go dari kode penanda tangan gocap, jadi selalu sesuai dengan tanda tangan yang dikirim.

Webhook Secret uji
527da983912e24551b118a0a4a76707e66f63e1326f371529615e77ed162bf3a
Toleransi selisih waktu
300 detik
Vektor uji tanda tangan webhook
VektorHasilAlasanX-Gocap-TimestampnowBadanX-Gocap-Signature
sah-asciisahTanda tangan HMAC-SHA256 atas timestamp, titik, dan badan persis, dengan Webhook Secret apa adanya sebagai kunci; selisih waktu 10 detik.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
sah-utf8-multibitasahBadan memuat karakter UTF-8 multibita (emoji, tanda pisah, huruf beraksen); HMAC dihitung atas byte badan persis.17575783801757578390{"device":"628xxxxxxxxx","sender":"628zzzzzzzzz","name":"Siti Aisyah","message":"Terima kasih 🙏 pesanan sudah sampai ✅ — salam dari Café Nusa","timestamp":1757578376,"inboxid":"91027734"}sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75
sah-spasi-dan-baris-barusahBadan berindentasi dengan spasi dan baris baru ditandatangani apa adanya; jangan merapikan badan sebelum verifikasi.17575783801757578390{ "device": "628xxxxxxxxx", "sender": "628yyyyyyyyy", "name": "Budi", "message": "Halo, apakah stok masih ada?", "timestamp": 1757578375, "inboxid": "91027733" }sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4
sah-karakter-htmlsahKarakter <, >, dan & dikirim literal, tidak di-escape; verifikasi atas badan mentah tetap sah.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Harga <promo> & diskon 10% hari ini","timestamp":1757578377,"inboxid":"91027735"}sha256=b4cc1abd2918f9fb9c17d036b0e638c9101b03fd81d0a79a0a6c229389f4d669
sah-badan-kosongsahBadan kosong: masukan HMAC hanya timestamp dan titik.17575783801757578390(kosong)sha256=8c2a99712ad320cfb1cc6e51cb409a67db6d7c273f730834dc14f5f1814208e0
sah-pesan-grupsahPayload pesan grup: sender berisi ID grup dan member berisi pengirim.17575783801757578390{"device":"628xxxxxxxxx","sender":"120363000000000000@g.us","member":"628zzzzzzzzz","name":"Siti Aisyah","message":"Rapat jam 10 ya","timestamp":1757578378,"inboxid":"91027736"}sha256=822ceb0dbd0f7ff3da868218cbfd4f96a43a1dd628fd7dad1d600e5478403956
sah-waktu-samasahTimestamp sama persis dengan jam penerima.17575783801757578380{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
sah-selisih-300-detik-lalusahSelisih tepat 300 detik ke masa lalu masih di dalam toleransi.17575783801757578680{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
sah-selisih-300-detik-depansahSelisih tepat 300 detik ke masa depan (jam penerima tertinggal) masih di dalam toleransi.17575783801757578080{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-badan-diubah-satu-bytetolakSatu huruf badan diubah setelah ditandatangani, jadi HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stoK masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-badan-diserialisasi-ulangtolakBadan di-parse lalu diserialisasi ulang sehingga spasi dan baris baru hilang; tanda tangan dihitung atas byte asli, jadi tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4
tolak-badan-escape-htmltolakBadan diserialisasi ulang dengan <, >, dan & di-escape menjadi \u003c, \u003e, dan \u0026; byte berubah sehingga HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Harga \u003cpromo\u003e \u0026 diskon 10% hari ini","timestamp":1757578377,"inboxid":"91027735"}sha256=b4cc1abd2918f9fb9c17d036b0e638c9101b03fd81d0a79a0a6c229389f4d669
tolak-badan-escape-unicodetolakBadan diserialisasi ulang menjadi ASCII saja (karakter non-ASCII menjadi \uXXXX, bawaan sebagian pustaka JSON); byte berubah sehingga HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628zzzzzzzzz","name":"Siti Aisyah","message":"Terima kasih \ud83d\ude4f pesanan sudah sampai \u2705 \u2014 salam dari Caf\u00e9 Nusa","timestamp":1757578376,"inboxid":"91027734"}sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75
tolak-badan-urutan-kuncitolakKunci JSON diurutkan ulang; isinya sama tetapi byte berbeda, jadi HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","inboxid":"91027733","message":"Halo, apakah stok masih ada?","name":"Budi","sender":"628yyyyyyyyy","timestamp":1757578375}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-badan-crlftolakBaris baru badan diubah menjadi CRLF; HMAC tidak cocok.17575783801757578390{ "device": "628xxxxxxxxx", "sender": "628yyyyyyyyy", "name": "Budi", "message": "Halo, apakah stok masih ada?", "timestamp": 1757578375, "inboxid": "91027733" }sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4
tolak-badan-baris-baru-di-akhirtolakBadan ditambah baris baru di akhir; HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"} sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-badan-unicode-dinormalisasitolakHuruf beraksen dinormalisasi ke bentuk terurai (NFD); tampak sama tetapi byte berbeda, jadi HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628zzzzzzzzz","name":"Siti Aisyah","message":"Terima kasih 🙏 pesanan sudah sampai ✅ — salam dari Café Nusa","timestamp":1757578376,"inboxid":"91027734"}sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75
tolak-secret-didekode-heksatolakTanda tangan dibuat dengan kunci 32 byte mentah di balik teks heksa, bukan teks secret; penerima yang benar memakai teks 64 heksa apa adanya sebagai kunci, jadi HMAC tidak cocok.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=ec5ca0ee7c6ccc0df44074d6e16074fdbd290b59ba53c42998a853e132e9d3c3
tolak-secret-huruf-besartolakTanda tangan dibuat dengan secret berhuruf besar; secret yang ditampilkan dashboard berhuruf kecil dan dipakai persis.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1c8679d44f18e30d1eb6d61155b64544adcd6b576a32f5270ed0f0c6ae8489e4
tolak-secret-baris-baru-di-akhirtolakTanda tangan dibuat dengan secret berakhiran baris baru, misalnya tersalin dari berkas; kuncinya harus persis 64 karakter.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=f880ec91b8fc8db3db832c0665016a2f5c61dd43d4c033b0e5813b3162ce7d11
tolak-secret-laintolakTanda tangan dari Webhook Secret lain, misalnya secret lama sebelum dibuat ulang.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=caba3938b27aceb7edf2d59a1e391791c701494d92c028c4a2a7f410c969d485
tolak-tanpa-awalan-sha256tolakHeader tanpa awalan sha256=; nilai header dibandingkan utuh.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-awalan-huruf-besartolakAwalan berhuruf besar (SHA256=); awalan yang sah persis sha256=.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}SHA256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-tanda-tangan-terpotongtolakHeksa tanda tangan kurang satu karakter.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc
tolak-tanda-tangan-kosongtolakHeader X-Gocap-Signature kosong atau tidak ada.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}(kosong)
tolak-tanda-tangan-non-asciitolakHeader 71 karakter dengan satu karakter non-ASCII: jumlah karakternya sama dengan tanda tangan sah, tetapi panjang byte-nya berbeda. Bandingkan panjang dalam byte agar pembanding waktu-konstan tidak galat.17575783801757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bcé
tolak-timestamp-10-menit-lalutolakTanda tangan sah untuk timestamp 600 detik sebelum jam penerima; di luar toleransi 300 detik, jadi ditolak untuk mencegah pengulangan.17575777901757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=db4e6d98b519d36a2d0d26813b16e1489eeeb2cbdbef60e62bbd38c9b1ae482d
tolak-selisih-301-detik-lalutolakSelisih 301 detik ke masa lalu, satu detik di luar toleransi.17575783801757578681{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-selisih-301-detik-depantolakSelisih 301 detik ke masa depan, satu detik di luar toleransi.17575783801757578079{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-timestamp-10-menit-depantolakTanda tangan sah untuk timestamp 600 detik sesudah jam penerima; di luar toleransi 300 detik.17575789901757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=9d5ee5e1de3a8eecd97bd347c084349139a9d646f78ee4b121f1e0212c73de74
tolak-timestamp-digantitolakHeader timestamp diganti setelah ditandatangani; timestamp ikut ditandatangani, jadi HMAC tidak cocok.17575783811757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-timestamp-milidetiktolakTimestamp dalam milidetik; tanda tangannya sah, tetapi selisihnya dengan detik unix jauh di luar toleransi.17575783800001757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=e37a9132f59fb5ee6846872e3cb0f0a6b47ade43b0cf601725181dab4f58318c
tolak-timestamp-desimaltolakTimestamp berisi titik desimal; HMAC-nya cocok dan nilainya dalam toleransi, tetapi header yang sah hanya deret digit.1757578380.01757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=0ac961527783627f3621820d6bceaea55a98732fad410a2991969bf718a7f628
tolak-timestamp-eksponentolakTimestamp dalam notasi eksponen bernilai sama dengan detik unix yang sah dan HMAC-nya cocok, tetapi bukan deret digit.1.75757838e91757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=a94e1b027d07b49038dea83de6f928f7be19bbe421eedd251341eb4d6d6638a7
tolak-timestamp-titik-di-akhirtolakTimestamp berakhiran titik; HMAC-nya cocok, tetapi header yang sah hanya deret digit.1757578380.1757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=af5a85d19d1f6ae3c2dc50a4b0b677a8b096431a9ea5a38819ceff4f66b44f83
tolak-timestamp-kosongtolakHeader X-Gocap-Timestamp kosong atau tidak ada.(kosong)1757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0
tolak-tanpa-headertolakPermintaan tanpa kedua header tanda tangan.(kosong)1757578390{"device":"628xxxxxxxxx","sender":"628yyyyyyyyy","name":"Budi","message":"Halo, apakah stok masih ada?","timestamp":1757578375,"inboxid":"91027733"}(kosong)
Semua vektor sebagai JSON (webhook-vectors.json)
{
  "generated_by": "apps/api/internal/webhook/vectors_test.go",
  "secret": "527da983912e24551b118a0a4a76707e66f63e1326f371529615e77ed162bf3a",
  "tolerance_seconds": 300,
  "vectors": [
    {
      "name": "sah-ascii",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": true,
      "why": "Tanda tangan HMAC-SHA256 atas timestamp, titik, dan badan persis, dengan Webhook Secret apa adanya sebagai kunci; selisih waktu 10 detik."
    },
    {
      "name": "sah-utf8-multibita",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628zzzzzzzzz\",\"name\":\"Siti Aisyah\",\"message\":\"Terima kasih 🙏 pesanan sudah sampai ✅ — salam dari Café Nusa\",\"timestamp\":1757578376,\"inboxid\":\"91027734\"}",
      "signature": "sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75",
      "valid": true,
      "why": "Badan memuat karakter UTF-8 multibita (emoji, tanda pisah, huruf beraksen); HMAC dihitung atas byte badan persis."
    },
    {
      "name": "sah-spasi-dan-baris-baru",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\n  \"device\": \"628xxxxxxxxx\",\n  \"sender\": \"628yyyyyyyyy\",\n  \"name\": \"Budi\",\n  \"message\": \"Halo, apakah stok masih ada?\",\n  \"timestamp\": 1757578375,\n  \"inboxid\": \"91027733\"\n}",
      "signature": "sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4",
      "valid": true,
      "why": "Badan berindentasi dengan spasi dan baris baru ditandatangani apa adanya; jangan merapikan badan sebelum verifikasi."
    },
    {
      "name": "sah-karakter-html",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Harga <promo> & diskon 10% hari ini\",\"timestamp\":1757578377,\"inboxid\":\"91027735\"}",
      "signature": "sha256=b4cc1abd2918f9fb9c17d036b0e638c9101b03fd81d0a79a0a6c229389f4d669",
      "valid": true,
      "why": "Karakter <, >, dan & dikirim literal, tidak di-escape; verifikasi atas badan mentah tetap sah."
    },
    {
      "name": "sah-badan-kosong",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "",
      "signature": "sha256=8c2a99712ad320cfb1cc6e51cb409a67db6d7c273f730834dc14f5f1814208e0",
      "valid": true,
      "why": "Badan kosong: masukan HMAC hanya timestamp dan titik."
    },
    {
      "name": "sah-pesan-grup",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"120363000000000000@g.us\",\"member\":\"628zzzzzzzzz\",\"name\":\"Siti Aisyah\",\"message\":\"Rapat jam 10 ya\",\"timestamp\":1757578378,\"inboxid\":\"91027736\"}",
      "signature": "sha256=822ceb0dbd0f7ff3da868218cbfd4f96a43a1dd628fd7dad1d600e5478403956",
      "valid": true,
      "why": "Payload pesan grup: sender berisi ID grup dan member berisi pengirim."
    },
    {
      "name": "sah-waktu-sama",
      "timestamp": "1757578380",
      "now": 1757578380,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": true,
      "why": "Timestamp sama persis dengan jam penerima."
    },
    {
      "name": "sah-selisih-300-detik-lalu",
      "timestamp": "1757578380",
      "now": 1757578680,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": true,
      "why": "Selisih tepat 300 detik ke masa lalu masih di dalam toleransi."
    },
    {
      "name": "sah-selisih-300-detik-depan",
      "timestamp": "1757578380",
      "now": 1757578080,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": true,
      "why": "Selisih tepat 300 detik ke masa depan (jam penerima tertinggal) masih di dalam toleransi."
    },
    {
      "name": "tolak-badan-diubah-satu-byte",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stoK masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Satu huruf badan diubah setelah ditandatangani, jadi HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-diserialisasi-ulang",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4",
      "valid": false,
      "why": "Badan di-parse lalu diserialisasi ulang sehingga spasi dan baris baru hilang; tanda tangan dihitung atas byte asli, jadi tidak cocok."
    },
    {
      "name": "tolak-badan-escape-html",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Harga \\u003cpromo\\u003e \\u0026 diskon 10% hari ini\",\"timestamp\":1757578377,\"inboxid\":\"91027735\"}",
      "signature": "sha256=b4cc1abd2918f9fb9c17d036b0e638c9101b03fd81d0a79a0a6c229389f4d669",
      "valid": false,
      "why": "Badan diserialisasi ulang dengan <, >, dan & di-escape menjadi \\u003c, \\u003e, dan \\u0026; byte berubah sehingga HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-escape-unicode",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628zzzzzzzzz\",\"name\":\"Siti Aisyah\",\"message\":\"Terima kasih \\ud83d\\ude4f pesanan sudah sampai \\u2705 \\u2014 salam dari Caf\\u00e9 Nusa\",\"timestamp\":1757578376,\"inboxid\":\"91027734\"}",
      "signature": "sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75",
      "valid": false,
      "why": "Badan diserialisasi ulang menjadi ASCII saja (karakter non-ASCII menjadi \\uXXXX, bawaan sebagian pustaka JSON); byte berubah sehingga HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-urutan-kunci",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"inboxid\":\"91027733\",\"message\":\"Halo, apakah stok masih ada?\",\"name\":\"Budi\",\"sender\":\"628yyyyyyyyy\",\"timestamp\":1757578375}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Kunci JSON diurutkan ulang; isinya sama tetapi byte berbeda, jadi HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-crlf",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\r\n  \"device\": \"628xxxxxxxxx\",\r\n  \"sender\": \"628yyyyyyyyy\",\r\n  \"name\": \"Budi\",\r\n  \"message\": \"Halo, apakah stok masih ada?\",\r\n  \"timestamp\": 1757578375,\r\n  \"inboxid\": \"91027733\"\r\n}",
      "signature": "sha256=2073caa730c1460981a088d153960cd6f4714ea20a11ff344b1b86e916e9d2f4",
      "valid": false,
      "why": "Baris baru badan diubah menjadi CRLF; HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-baris-baru-di-akhir",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}\n",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Badan ditambah baris baru di akhir; HMAC tidak cocok."
    },
    {
      "name": "tolak-badan-unicode-dinormalisasi",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628zzzzzzzzz\",\"name\":\"Siti Aisyah\",\"message\":\"Terima kasih 🙏 pesanan sudah sampai ✅ — salam dari Café Nusa\",\"timestamp\":1757578376,\"inboxid\":\"91027734\"}",
      "signature": "sha256=3dd61109caf6431b458f766f8674af31ac6fa2b265b451ef77ceea79f8ec0b75",
      "valid": false,
      "why": "Huruf beraksen dinormalisasi ke bentuk terurai (NFD); tampak sama tetapi byte berbeda, jadi HMAC tidak cocok."
    },
    {
      "name": "tolak-secret-didekode-heksa",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=ec5ca0ee7c6ccc0df44074d6e16074fdbd290b59ba53c42998a853e132e9d3c3",
      "valid": false,
      "why": "Tanda tangan dibuat dengan kunci 32 byte mentah di balik teks heksa, bukan teks secret; penerima yang benar memakai teks 64 heksa apa adanya sebagai kunci, jadi HMAC tidak cocok."
    },
    {
      "name": "tolak-secret-huruf-besar",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1c8679d44f18e30d1eb6d61155b64544adcd6b576a32f5270ed0f0c6ae8489e4",
      "valid": false,
      "why": "Tanda tangan dibuat dengan secret berhuruf besar; secret yang ditampilkan dashboard berhuruf kecil dan dipakai persis."
    },
    {
      "name": "tolak-secret-baris-baru-di-akhir",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=f880ec91b8fc8db3db832c0665016a2f5c61dd43d4c033b0e5813b3162ce7d11",
      "valid": false,
      "why": "Tanda tangan dibuat dengan secret berakhiran baris baru, misalnya tersalin dari berkas; kuncinya harus persis 64 karakter."
    },
    {
      "name": "tolak-secret-lain",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=caba3938b27aceb7edf2d59a1e391791c701494d92c028c4a2a7f410c969d485",
      "valid": false,
      "why": "Tanda tangan dari Webhook Secret lain, misalnya secret lama sebelum dibuat ulang."
    },
    {
      "name": "tolak-tanpa-awalan-sha256",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Header tanpa awalan sha256=; nilai header dibandingkan utuh."
    },
    {
      "name": "tolak-awalan-huruf-besar",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "SHA256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Awalan berhuruf besar (SHA256=); awalan yang sah persis sha256=."
    },
    {
      "name": "tolak-tanda-tangan-terpotong",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc",
      "valid": false,
      "why": "Heksa tanda tangan kurang satu karakter."
    },
    {
      "name": "tolak-tanda-tangan-kosong",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "",
      "valid": false,
      "why": "Header X-Gocap-Signature kosong atau tidak ada."
    },
    {
      "name": "tolak-tanda-tangan-non-ascii",
      "timestamp": "1757578380",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bcé",
      "valid": false,
      "why": "Header 71 karakter dengan satu karakter non-ASCII: jumlah karakternya sama dengan tanda tangan sah, tetapi panjang byte-nya berbeda. Bandingkan panjang dalam byte agar pembanding waktu-konstan tidak galat."
    },
    {
      "name": "tolak-timestamp-10-menit-lalu",
      "timestamp": "1757577790",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=db4e6d98b519d36a2d0d26813b16e1489eeeb2cbdbef60e62bbd38c9b1ae482d",
      "valid": false,
      "why": "Tanda tangan sah untuk timestamp 600 detik sebelum jam penerima; di luar toleransi 300 detik, jadi ditolak untuk mencegah pengulangan."
    },
    {
      "name": "tolak-selisih-301-detik-lalu",
      "timestamp": "1757578380",
      "now": 1757578681,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Selisih 301 detik ke masa lalu, satu detik di luar toleransi."
    },
    {
      "name": "tolak-selisih-301-detik-depan",
      "timestamp": "1757578380",
      "now": 1757578079,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Selisih 301 detik ke masa depan, satu detik di luar toleransi."
    },
    {
      "name": "tolak-timestamp-10-menit-depan",
      "timestamp": "1757578990",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=9d5ee5e1de3a8eecd97bd347c084349139a9d646f78ee4b121f1e0212c73de74",
      "valid": false,
      "why": "Tanda tangan sah untuk timestamp 600 detik sesudah jam penerima; di luar toleransi 300 detik."
    },
    {
      "name": "tolak-timestamp-diganti",
      "timestamp": "1757578381",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Header timestamp diganti setelah ditandatangani; timestamp ikut ditandatangani, jadi HMAC tidak cocok."
    },
    {
      "name": "tolak-timestamp-milidetik",
      "timestamp": "1757578380000",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=e37a9132f59fb5ee6846872e3cb0f0a6b47ade43b0cf601725181dab4f58318c",
      "valid": false,
      "why": "Timestamp dalam milidetik; tanda tangannya sah, tetapi selisihnya dengan detik unix jauh di luar toleransi."
    },
    {
      "name": "tolak-timestamp-desimal",
      "timestamp": "1757578380.0",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=0ac961527783627f3621820d6bceaea55a98732fad410a2991969bf718a7f628",
      "valid": false,
      "why": "Timestamp berisi titik desimal; HMAC-nya cocok dan nilainya dalam toleransi, tetapi header yang sah hanya deret digit."
    },
    {
      "name": "tolak-timestamp-eksponen",
      "timestamp": "1.75757838e9",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=a94e1b027d07b49038dea83de6f928f7be19bbe421eedd251341eb4d6d6638a7",
      "valid": false,
      "why": "Timestamp dalam notasi eksponen bernilai sama dengan detik unix yang sah dan HMAC-nya cocok, tetapi bukan deret digit."
    },
    {
      "name": "tolak-timestamp-titik-di-akhir",
      "timestamp": "1757578380.",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=af5a85d19d1f6ae3c2dc50a4b0b677a8b096431a9ea5a38819ceff4f66b44f83",
      "valid": false,
      "why": "Timestamp berakhiran titik; HMAC-nya cocok, tetapi header yang sah hanya deret digit."
    },
    {
      "name": "tolak-timestamp-kosong",
      "timestamp": "",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "sha256=1a2140f43d279c5cb01b1060b6f62b7e5a4da98ee41b9b96e5897a10255c0bc0",
      "valid": false,
      "why": "Header X-Gocap-Timestamp kosong atau tidak ada."
    },
    {
      "name": "tolak-tanpa-header",
      "timestamp": "",
      "now": 1757578390,
      "body": "{\"device\":\"628xxxxxxxxx\",\"sender\":\"628yyyyyyyyy\",\"name\":\"Budi\",\"message\":\"Halo, apakah stok masih ada?\",\"timestamp\":1757578375,\"inboxid\":\"91027733\"}",
      "signature": "",
      "valid": false,
      "why": "Permintaan tanpa kedua header tanda tangan."
    }
  ]
}

Google Apps Script

Google Apps Script tidak bisa menerima webhook gocap. Objek event doPost(e) tidak memuat header, sehingga X-Gocap-Signature tidak terbaca, dan balasan web app dialihkan ke URL lain, sedangkan gocap menghitung pengalihan 3xx sebagai gagal lalu mencoba ulang. Pakai penerima PHP, Node.js, atau Python di atas. Apps Script tetap dapat mengirim pesan (contoh kirim pesan dengan Apps Script).

Uji dari awal sampai akhir

  1. Jalankan penerima dan buat URL https publik ke sana

    Untuk pengembangan di komputer sendiri, pakai tunnel HTTPS ke port penerima.

  2. Simpan URL itu sebagai URL webhook pesan masuk

    Buka pengaturan device dari daftar device, lalu isi URL webhook pesan masuk.

  3. Tekan "Uji webhook"

    gocap mengirim satu payload contoh bertanda tangan ke URL yang tersimpan, lalu menampilkan kode HTTP dan durasinya. Penerima yang benar menjawab 2xx. Payload uji memakai inboxid bernilai "0", jadi mudah dibedakan dari pesan sungguhan. Tombol ini mengabaikan badan jawaban penerima, jadi tidak dapat dipakai untuk menguji balasan webhook.

Ringkasan pengiriman webhook ada di Terima pesan masuk.