Webhook
kokiwa mengirim permintaan POST ke URL https Anda saat pembayaran dikreditkan atau statusnya berubah. Kelola URL di halaman Webhook pada dasbor merchant.
Butuh kode siap pakai? Contoh integrasi dalam Node.js, Python, PHP, Go, dan Java
Format permintaan
{
"id": "evt_3Fk9Qw2Lm7Rt5Xb1Nc8Vz4",
"type": "payment_intent.paid",
"created_at": "2026-11-02T08:41:07Z",
"livemode": true,
"data": {
"payment_intent": { "id": "pi_7Gm4Rk2Wp9Ls1Qv8Bd3Zt6", "object": "payment_intent", "status": "paid", "...": "..." },
"deposit": { "id": "dep_9Kx2Pa6Wn3Ty8Lq5Rb1Ms4", "object": "deposit", "status": "settled", "...": "..." }
}
}| Header | Keterangan |
|---|---|
Kokiwa-Signature | Tanda tangan berbentuk t=stempel_waktu,v1=tanda_tangan. |
Kokiwa-Event-Id | ID peristiwa, sama dengan id di isi permintaan. |
Kokiwa-Delivery-Attempt | Percobaan pengiriman, mulai dari 1. |
data.deposit adalah objek setoran. data.payment_intent adalah objek yang dikembalikan saat mengambil pembayaran, tanpa checkout_url dan livemode; livemode ada di tingkat teratas peristiwa.
Jenis peristiwa
| type | Kapan dikirim | data |
|---|---|---|
payment_intent.paid | Pembayaran lunas | payment_intent、deposit |
payment_intent.partially_paid | Pembayaran menerima sebagian jumlahnya | payment_intent、deposit |
payment_intent.expired | Pembayaran kedaluwarsa tanpa dibayar | payment_intent |
payment_intent.paid_late | Dana masuk setelah pembayaran kedaluwarsa | payment_intent、deposit |
deposit.confirmed | Setoran terkonfirmasi on-chain, dari pembayaran atau alamat setoran | deposit |
deposit.settled | Setoran sampai di alamat penerimaan Anda | deposit |
webhook.test | Peristiwa uji yang Anda kirim dari dasbor | message |
Pembayaran dompet diselesaikan dalam transaksi pembayaran, jadi deposit.confirmed dan deposit.settled datang bersamaan. Transfer mengirim deposit.confirmed lebih dulu dan deposit.settled setelah penyapuan. Kreditkan pengguna saat confirmed; tidak perlu menunggu settled.
Verifikasi tanda tangan
Setiap URL webhook punya rahasia tanda tangan sendiri (whsec_…), ditampilkan sekali saat dibuat.
- Ambil
tdanv1dariKokiwa-Signature. - Hitung HMAC-SHA256 dari string
{t}.{isi mentah}dengan rahasia, ubah ke hex huruf kecil, dan bandingkan denganv1dalam waktu konstan. - Tolak permintaan jika
tberselisih lebih dari 5 menit dari waktu sekarang.
Gunakan isi mentah permintaan. Mengurai JSON lalu menyerialkannya lagi mengubah byte, dan tanda tangan tidak akan cocok.
Node.js
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.KOKIWA_WEBHOOK_SECRET;
app.post("/kokiwa/webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Kokiwa-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.${req.body}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const match = parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
if (!fresh || !match) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// deduplicate by event.id, process, then return 2xx quickly
res.sendStatus(200);
});Go
func verify(secret, body []byte, header string) bool {
var t, v1 string
for _, p := range strings.Split(header, ",") {
k, v, _ := strings.Cut(strings.TrimSpace(p), "=")
switch k {
case "t":
t = v
case "v1":
v1 = v
}
}
ts, err := strconv.ParseInt(t, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return false
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(t + "."))
mac.Write(body)
return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(v1))
}Python
import hashlib, hmac, time
def verify(secret: bytes, body: bytes, header: str) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(secret, f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)Respons dan percobaan ulang
- Kembalikan 2xx apa pun dalam 10 detik dan pindahkan pekerjaan lambat ke antrean latar belakang.
- Respons non-2xx, batas waktu, dan kegagalan koneksi dicoba ulang 11 kali: setelah 30 detik, 2, 10, dan 30 menit, lalu 1, 2, 4, 8, 12, 24, dan 20 jam. Pengiriman berhenti setelah sekitar 3 hari.
- Satu peristiwa bisa datang lebih dari sekali dan urutannya tidak dijamin. Buang duplikat berdasarkan
idperistiwa, dan panggilGET /v1/payment_intents/{id}untuk status terbaru. - Halaman Webhook menampilkan hasil tiap pengiriman dan memungkinkan pengiriman ulang manual.
- Jika semua percobaan gagal, gunakan
GET /v1/depositsuntuk mengejar setoran yang terlewat.
Notifikasi Telegram
Tautkan @KoKiWaPayBot di halaman Pengaturan untuk menerima pesan setiap ada setoran. Ini hanya pengingat dan tidak menggantikan webhook.