Callback Notification

587.043 request sukses

Sistem kami akan mengirimkan notifikasi callback ke endpoint yang Anda daftarkan setiap kali terjadi perubahan status transaksi (success atau failed).

Apa itu Callback? Callback adalah mekanisme notifikasi otomatis dari server KokinPay ke server Anda untuk memberitahukan perubahan status transaksi. Dengan callback, Anda tidak perlu melakukan polling berulang-ulang untuk mengecek status transaksi.

Cara Kerja Callback

Request Transaksi

Anda melakukan request transaksi ke API kami

Proses Transaksi

Sistem kami memproses transaksi

Kirim Callback

Kami mengirim notifikasi hasil transaksi ke URL callback Anda

Konfigurasi Callback URL

Sebelum dapat menerima callback, Anda perlu mengatur callback URL di dashboard akun KokinPay Anda:

  1. Login ke dashboard kokinpay.com
  2. Masuk ke menu Pengaturan API atau Koneksi
  3. Masukkan URL endpoint callback Anda (contoh: https://yourdomain.com/api/callback)
  4. Simpan pengaturan
Whitelist IP Server: Untuk keamanan, silakan whitelist IP server kami 103.147.154.187 agar callback dapat diterima dengan baik. Pastikan firewall atau security system Anda mengizinkan request dari IP tersebut.

HTTP Headers

Setiap request callback yang kami kirim akan menyertakan HTTP headers berikut:

Header Value Deskripsi
Content-Type application/json Format payload adalah JSON
X-Kokinpay-Event transaction.update Identifier untuk jenis event callback

Callback Payload

Sistem kami akan mengirimkan payload dalam format JSON dengan struktur sebagai berikut:

Callback - Transaksi Berhasil (Success)

{
  "message": "Pesanan berhasil diproses",
  "data": {
    "client_trx_id": "TRX20250129001",
    "kokinpay_trx_id": "KPTRX2025012900001",
    "product_code": "TSEL10",
    "product_name": "Telkomsel 10.000",
    "price": 10500,
    "target": "081234567890",
    "sn": "1234567890123456",
    "status": "success"
  }
}

Callback - Transaksi Gagal (Failed)

{
  "message": "Pesanan gagal diproses. Saldo telah dikembalikan",
  "data": {
    "client_trx_id": "TRX20250129001",
    "kokinpay_trx_id": "KPTRX2025012900001",
    "product_code": "TSEL10",
    "product_name": "Telkomsel 10.000",
    "price": 10500,
    "target": "081234567890",
    "sn": "-",
    "status": "failed"
  }
}

Struktur Data Callback

Field Type Deskripsi
message string Pesan informasi hasil transaksi
client_trx_id string ID transaksi dari sistem client
kokinpay_trx_id string ID transaksi dari sistem KokinPay
product_code string Kode produk yang dibeli
product_name string Nama produk yang dibeli
price integer Harga produk dalam Rupiah (IDR)
target string Tujuan transaksi
sn string Serial Number / kode voucher. Berisi - jika transaksi gagal atau tidak ada SN
status string Status transaksi: success atau failed

Response yang Diharapkan

Endpoint callback Anda wajib mengembalikan HTTP status code 200 (OK) untuk mengindikasikan bahwa callback telah diterima dengan baik.

Penting: Jika endpoint Anda tidak mengembalikan status code 200, sistem kami akan melakukan retry hingga 3 kali dengan jeda 1 detik antar retry. Pastikan endpoint callback Anda selalu mengembalikan response 200 meskipun terjadi error internal di sistem Anda.
Contoh Response yang Benar
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "received"
}

Contoh Implementasi Endpoint Callback

Contoh Implementasi PHP (Laravel)
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class CallbackController extends Controller
{
    public function handleKokinpayCallback(Request $request)
    {
        if ($request->header('X-Kokinpay-Event') !== 'transaction.update') {
            return response()->json(['status' => 'invalid event'], 400);
        }

        $allowedIp = '103.147.154.187';
        if ($request->ip() !== $allowedIp) {
            Log::warning('Callback from unauthorized IP', ['ip' => $request->ip()]);
            return response()->json(['status' => 'unauthorized'], 403);
        }

        $message = $request->input('message');
        $data = $request->input('data');

        Log::info('Callback received from KokinPay', [
            'client_trx_id' => $data['client_trx_id'],
            'kokinpay_trx_id' => $data['kokinpay_trx_id'],
            'status' => $data['status']
        ]);

        try {
            $transaction = Transaction::where('trx_id', $data['client_trx_id'])->first();
            
            if ($transaction) {
                $transaction->update([
                    'status' => $data['status'],
                    'kokinpay_trx_id' => $data['kokinpay_trx_id'],
                    'sn' => $data['sn'],
                    'updated_at' => now()
                ]);

                if ($data['status'] === 'success') {
                    $this->sendSuccessNotification($transaction);
                } else {
                    $this->sendFailedNotification($transaction);
                }
            }

            return response()->json([
                'status' => 'received',
                'client_trx_id' => $data['client_trx_id']
            ], 200);

        } catch (\Exception $e) {
            Log::error('Error processing callback', [
                'error' => $e->getMessage(),
                'client_trx_id' => $data['client_trx_id'] ?? null
            ]);

            return response()->json([
                'status' => 'received_with_error',
                'message' => 'Callback received but processing failed'
            ], 200);
        }
    }
}
?>
Contoh Implementasi Node.js (Express)
const express = require('express');
const app = express();

app.use(express.json());

const validateIp = (req, res, next) => {
  const allowedIp = '103.147.154.187';
  const clientIp = req.ip || req.connection.remoteAddress;
  
  if (clientIp !== allowedIp) {
    console.warn(`Callback from unauthorized IP: ${clientIp}`);
    return res.status(403).json({ status: 'unauthorized' });
  }
  
  next();
};

app.post('/api/callback/kokinpay', validateIp, async (req, res) => {
  try {
    const eventHeader = req.headers['x-kokinpay-event'];
    if (eventHeader !== 'transaction.update') {
      return res.status(400).json({ status: 'invalid event' });
    }

    const { message, data } = req.body;

    console.log('Callback received:', {
      client_trx_id: data.client_trx_id,
      kokinpay_trx_id: data.kokinpay_trx_id,
      status: data.status
    });

    await updateTransaction({
      trxId: data.client_trx_id,
      kokinpayTrxId: data.kokinpay_trx_id,
      status: data.status,
      sn: data.sn
    });

    if (data.status === 'success') {
      await sendSuccessNotification(data);
    } else {
      await sendFailedNotification(data);
    }

    return res.status(200).json({
      status: 'received',
      client_trx_id: data.client_trx_id
    });

  } catch (error) {
    console.error('Error processing callback:', error);
    
    return res.status(200).json({
      status: 'received_with_error',
      message: 'Callback received but processing failed'
    });
  }
});

app.listen(3000, () => {
  console.log('Callback server running on port 3000');
});
Contoh Implementasi Python (Flask)
from flask import Flask, request, jsonify
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

ALLOWED_IP = '103.147.154.187'

@app.route('/api/callback/kokinpay', methods=['POST'])
def handle_callback():
    try:
        client_ip = request.remote_addr
        if client_ip != ALLOWED_IP:
            logging.warning(f'Callback from unauthorized IP: {client_ip}')
            return jsonify({'status': 'unauthorized'}), 403

        event_header = request.headers.get('X-Kokinpay-Event')
        if event_header != 'transaction.update':
            return jsonify({'status': 'invalid event'}), 400

        payload = request.get_json()
        message = payload.get('message')
        data = payload.get('data')

        logging.info(f"Callback received: {data.get('client_trx_id')} - {data.get('status')}")

        update_transaction(
            trx_id=data.get('client_trx_id'),
            kokinpay_trx_id=data.get('kokinpay_trx_id'),
            status=data.get('status'),
            sn=data.get('sn')
        )

        if data.get('status') == 'success':
            send_success_notification(data)
        else:
            send_failed_notification(data)

        return jsonify({
            'status': 'received',
            'client_trx_id': data.get('client_trx_id')
        }), 200

    except Exception as e:
        logging.error(f'Error processing callback: {str(e)}')
        
        return jsonify({
            'status': 'received_with_error',
            'message': 'Callback received but processing failed'
        }), 200

if __name__ == '__main__':
    app.run(port=3000)

Testing Callback

Untuk testing callback di development environment, Anda dapat menggunakan tools berikut:

  • ngrok - Untuk expose localhost ke internet
  • webhook.site - Untuk melihat payload callback yang dikirim
  • Postman / Insomnia - Untuk simulasi callback secara manual
Tips Testing dengan ngrok:
  1. Install dan jalankan ngrok: ngrok http 3000
  2. Copy URL yang diberikan (contoh: https://abc123.ngrok.io)
  3. Daftarkan URL callback di dashboard: https://abc123.ngrok.io/api/callback/kokinpay
  4. Lakukan transaksi test dan pantau callback yang masuk

Best Practices

Rekomendasi untuk Implementasi Callback:
  • Validasi IP: Selalu validasi bahwa request berasal dari IP 103.147.154.187
  • Validasi Header: Cek header X-Kokinpay-Event untuk memastikan ini callback yang valid
  • Idempotent: Pastikan callback yang sama tidak memproses transaksi berulang kali
  • Response Cepat: Proses callback dengan cepat (max 10 detik), gunakan queue untuk proses berat
  • Logging: Simpan log semua callback untuk debugging dan audit trail
  • Error Handling: Tetap return 200 meskipun terjadi error internal
  • Security: Gunakan HTTPS untuk endpoint callback Anda
  • Monitoring: Setup monitoring untuk memastikan callback endpoint selalu up

Troubleshooting

Masalah Solusi
Callback tidak pernah diterima
  • Pastikan callback URL sudah terdaftar di dashboard
  • Whitelist IP 103.147.154.187 di firewall
  • Cek apakah endpoint dapat diakses dari luar (gunakan curl test)
  • Pastikan menggunakan HTTPS dengan SSL valid
Callback diterima berkali-kali
  • Pastikan endpoint Anda return status 200
  • Implementasikan idempotency check berdasarkan client_trx_id
  • Response time jangan lebih dari 10 detik
Data callback tidak sesuai
  • Cek apakah Anda parse JSON dengan benar
  • Validasi struktur data sesuai dokumentasi
  • Simpan raw payload untuk debugging