- Go 1.22 atau lebih baru.
- SQLite untuk storage lokal.
Pastikan project ter-setup dan bisa ditest:
go mod tidy
go test ./...
./scripts/smoke-local.shRutebayar CLI:
go build -o ./bin/rutebayar ./cmd/rute-bayar
./bin/rutebayar version
./bin/rutebayar provider list
./bin/rutebayar webhook serve --addr :8080
./bin/rutebayar db migrate
./bin/rutebayar onboard xendit --secret-key "$XENDIT_SECRET_KEY" --environment sandbox
./bin/rutebayar provider accounts
./bin/rutebayar provider test xendit
./bin/rutebayar onboard midtrans --merchant-id "$MIDTRANS_MERCHANT_ID" --client-key "$MIDTRANS_CLIENT_KEY" --server-key "$MIDTRANS_SERVER_KEY" --environment sandbox
./bin/rutebayar provider test midtrans
./bin/rutebayar onboard doku --client-id "$DOKU_CLIENT_ID" --secret-key "$DOKU_SECRET_KEY" --environment sandbox
./bin/rutebayar provider test doku
./bin/rutebayar webhook forward add --provider midtrans --name orders --url https://example.com/webhooks/orders --event-filter event=payment_session.created
./bin/rutebayar webhook forward list --provider midtrans
./bin/rutebayar webhook replay --event-id webhook_0001 --provider midtransUntuk memverifikasi operasional forwarding:
- Migrasi DB dan jalankan daemon.
- Simulasikan webhook inbound.
- Gunakan
webhook replayuntuk memaksa eksekusi ulang event yang tersimpan. - Cek
webhook_forwarding_attemptsuntuk memastikan status attempt tersimpan. - Gunakan
webhook forward attempts list/show/retryuntuk diagnosa operasional.
Jalankan daemon:
./bin/rutebayar webhook serve --addr :8080 --environment sandboxDi terminal lain, cek health endpoint:
curl -i http://localhost:8080/healthzRespon sukses:
{"status":"ok"}Simulasi webhook lokal:
curl -i -X POST http://localhost:8080/webhooks/xendit \
-H 'Content-Type: application/json' \
-d '{"event":"payment_session.status.changed","status":"COMPLETED","reference_id":"INV-1001","id":"evt_001"}'atau:
export MIDTRANS_ORDER_ID="ORD-1001"
export MIDTRANS_STATUS_CODE="200"
export MIDTRANS_GROSS_AMOUNT="10000"
export MIDTRANS_SERVER_KEY="$MIDTRANS_SERVER_KEY"
export MIDTRANS_SIGNATURE=$(
printf '%s%s%s%s' \
"$MIDTRANS_ORDER_ID" \
"$MIDTRANS_STATUS_CODE" \
"$MIDTRANS_GROSS_AMOUNT" \
"$MIDTRANS_SERVER_KEY" \
| openssl dgst -sha512 -hex \
| awk '{print $2}'
)
curl -i -X POST http://localhost:8080/webhooks/midtrans \
-H 'Content-Type: application/json' \
-d "{\"order_id\":\"$MIDTRANS_ORDER_ID\",\"status_code\":\"$MIDTRANS_STATUS_CODE\",\"gross_amount\":\"$MIDTRANS_GROSS_AMOUNT\",\"transaction_status\":\"capture\",\"fraud_status\":\"accept\",\"payment_type\":\"bank_transfer\",\"signature_key\":\"$MIDTRANS_SIGNATURE\",\"transaction_id\":\"trx_001\",\"transaction_time\":\"2026-05-05T00:00:00Z\"}"Untuk payload yang valid dan lolos verifikasi provider (jika konfigurasi tersedia), kedua endpoint di atas seharusnya mengembalikan 202 Accepted.
Jika verifikasi gagal (misalnya signature/token tidak cocok), daemon akan mengembalikan 400.
sqlite3 ./rute-bayar.sqlite3 \
"SELECT id, provider_id, provider_event_id, event_type, processing_status, signature_valid, received_at FROM webhook_events ORDER BY received_at DESC LIMIT 20;"- Response
400 Bad Requestdengan error signature:- pastikan
server_keysudah onboard untuksandbox/productionyang sama dengan daemon environment. - pastikan payload ada semua field berikut:
order_id,status_code,gross_amount,signature_key. - pastikan nilai
gross_amountdi webhook sama persis (format string/numerik) dengan nilai yang dihitungMidtrans. - hitung ulang
signature_keydengansha512(order_id + status_code + gross_amount + server_key)(tanpa pemisah). gross_amountdi payload Midtrans biasanya berupa string, contoh:"10000.00"atau"10000".
- pastikan
- Jika webhook
200/202tidak masuk ke log parse:- cek apakah payload webhook sudah termasuk
transaction_statusdanfraud_statusagar mapping status bisa lebih lengkap. - cek
handlertidak terbentuk bila akun Midtrans belum di-onboard; di mode itu verification tidak akan jalan.
- cek apakah payload webhook sudah termasuk
- Response
400 Bad Requestdengan kesalahancallback token:- pastikan Xendit mengirim header
X-Callback-Tokenbila token di-set saat onboarding. - jika tidak pakai token saat ini, hapus
--webhook-tokensaat onboarding lalu restart daemon.
- pastikan Xendit mengirim header
- Jika webhook 202 tapi tidak ada efek
payment_status:- payload biasanya tidak punya
reference_idatauorder_id; gunakanreference_id/external_idyang sama dengancreate paymentreference. - status terbaru diterima di field
status(contoh:ACTIVE,COMPLETED,FAILED,EXPIRED).
- payload biasanya tidak punya
Untuk webhook test dari internet sementara, gunakan Cloudflare tunnel:
wrangler tunnel quick-start http://localhost:8080Command di atas akan menampilkan URL seperti https://xxxx.trycloudflare.com.
Setelah URL muncul:
curl -i https://<domain>.trycloudflare.com/healthzAtur URL webhook provider menjadi:
https://<domain>.trycloudflare.com/webhooks/xendit
https://<domain>.trycloudflare.com/webhooks/midtrans
bind: operation not permitted- Biasanya dari batasan environment. Coba jalankan di terminal lokal lain atau gunakan port berbeda.
connection refuseddilocalhost:8080- Pastikan daemon masih aktif di session yang sama dan belum crash saat dipanggil.
502 Bad Gatewaydari URL tunnel- Pastikan daemon lokal tetap jalan dan tunnel tetap connected ke
--urlyang benar.
- Pastikan daemon lokal tetap jalan dan tunnel tetap connected ke
- Gagal resolve domain
trycloudflare.com- Bisa jadi environment memiliki pembatasan DNS/network. Coba perangkat/jaringan lain untuk validasi.
Skema SQLite awal ada di:
migrations/0001_initial.sql
Migration ini mencakup:
- providers
- provider accounts
- payment intents
- payment attempts
- webhook events
- webhook forwarding targets
- webhook forwarding attempts
- refunds
- audit logs
- Tambahkan CI GitHub Actions (test + lint + build matrix).
- Dokumentasikan hardening webhook lebih dalam (verifikasi error edge-case, observability).
- Evaluasi policy keamanan untuk header token/storage rotation.
- Kembangkan command
pay webhook testuntuk simulasi event yang konsisten.