kokiwa

Webhook

kokiwa mengirim permintaan POST ke URL https Anda saat pembayaran dikreditkan atau statusnya berubah. Kelola URL di halaman Webhook pada dasbor merchant.

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", "...": "..." }
  }
}
HeaderKeterangan
Kokiwa-SignatureTanda tangan berbentuk t=stempel_waktu,v1=tanda_tangan.
Kokiwa-Event-IdID peristiwa, sama dengan id di isi permintaan.
Kokiwa-Delivery-AttemptPercobaan 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

typeKapan dikirimdata
payment_intent.paidPembayaran lunaspayment_intent、deposit
payment_intent.partially_paidPembayaran menerima sebagian jumlahnyapayment_intent、deposit
payment_intent.expiredPembayaran kedaluwarsa tanpa dibayarpayment_intent
payment_intent.paid_lateDana masuk setelah pembayaran kedaluwarsapayment_intent、deposit
deposit.confirmedSetoran terkonfirmasi on-chain, dari pembayaran atau alamat setorandeposit
deposit.settledSetoran sampai di alamat penerimaan Andadeposit
webhook.testPeristiwa uji yang Anda kirim dari dasbormessage

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.

  1. Ambil t dan v1 dari Kokiwa-Signature.
  2. Hitung HMAC-SHA256 dari string {t}.{isi mentah} dengan rahasia, ubah ke hex huruf kecil, dan bandingkan dengan v1 dalam waktu konstan.
  3. Tolak permintaan jika t berselisih 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 id peristiwa, dan panggil GET /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/deposits untuk mengejar setoran yang terlewat.

Notifikasi Telegram

Tautkan @KoKiWaPayBot di halaman Pengaturan untuk menerima pesan setiap ada setoran. Ini hanya pengingat dan tidak menggantikan webhook.

Connect a wallet with WalletConnect