Take a test payment.
About ten minutes. You create a sandbox account, succeed one payment, and check a webhook signature. No wallet is called.
-
Create the account
Open the dashboard and create a sandbox account. The API key is shown once. It starts with
sk_test_. Store it on your server. The dashboard can create another key later, and it can revoke this one. -
Create a payment that succeeds
Create it with
POST /v1/payments. The example setsmethodtosedad, the default.bci_payandclickuse that same path. A sandbox phone ending in0000succeeds immediately for Sedad and BCI Pay.Idempotency-Keyis required. Reuse that same key if you retry this exact request.curl https://api-sandbox.kalbe.io/v1/payments \ -H "Authorization: Bearer sk_test_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quick-1" \ -d '{"amount":15000,"currency":"MRU","method":"sedad","reference":"order_1842","payer":{"phone":"22246000000"}}'{ "id": "pay_01J", "reference": "order_1842", "amount": 15000, "currency": "MRU", "method": "sedad", "status": "succeeded", "fee_bps": 100, "fee_amount": 150, "created_at": "2026-10-21T16:04:11Z" }15000is 15,000 MRU. The fee is 150 MRU, which is 1%. A final payment has nonext_action. -
Check the signature
Save a public HTTPS URL in the dashboard under Settings. Kalbe signs the event and lists it under Webhooks. Replay sends it to your URL. Verify the raw body, not a JSON object you parsed and serialized again.
const crypto = require("crypto"); function verify(secret, timestamp, rawBody, signature) { const age = Math.abs(Date.now() / 1000 - Number(timestamp)); if (age > 300) return false; const expected = crypto.createHmac("sha256", secret) .update(timestamp + "." + rawBody) .digest("hex"); const left = Buffer.from(expected); const right = Buffer.from(signature); return left.length === right.length && crypto.timingSafeEqual(left, right); }This vector is fixed. Body
{"id":"evt_01","type":"payment.succeeded"}, timestamp1761080000, secretwhsec_test. The signature is9d5ee8ab7377e6dcf6099a5c3c294a485cd84db6a407597aaef402cd5713f300.
Phone endings, Click codes, and the full event body are in the method guides.