Getting Started / Callback Mechanism
Webhooks

Callback Mechanism

Callbacks are sent to merchants when transaction status changes (e.g., payment received, expired, failed). The callback includes transaction details and payment method-specific information.

HTTP Headers

The following headers are sent with each callback:

http
Content-Type: application/json
X-Signature: <HMAC-SHA256 signature>
X-Timestamp: <ISO8601 timestamp>
X-Callback-Id: <UUID of callback log entry>

The signature is generated using HMAC-SHA256 with the business unit's secret API key.

Base Callback Structure

All callbacks follow this structure:

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "uuid-of-transaction",
    "partnerReferenceNo": "merchant-reference-number",
    "status": "PAID|EXPIRED|FAILED|PENDING",
    "amount": "100000.00",
    "currency": "IDR",
    "paymentMethod": "VA|QRIS|CC|WECHATPAY|ALIPAY",
    "bank": "BNI|BRI|BCA|CIMB|WECHATPAY|ALIPAY",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T11:30:00Z"
  }
}

1. Virtual Account (VA)

Payment Method: VA

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "partnerReferenceNo": "ORDER-123456",
    "status": "PAID",
    "amount": "150000.00",
    "currency": "IDR",
    "paymentMethod": "VA",
    "bank": "BNI",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T11:30:00Z"
  }
}

2. QRIS (QR Indonesian Standard)

Payment Method: QRIS

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440001",
    "partnerReferenceNo": "ORDER-123457",
    "status": "PAID",
    "amount": "250000.00",
    "currency": "IDR",
    "paymentMethod": "QRIS",
    "bank": "CIMB",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T10:35:00Z"
  }
}

3. Credit Card (CC)

Payment Method: CC

For credit card payments, the callback includes provider-specific metadata received from the bank.

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440002",
    "partnerReferenceNo": "ORDER-123458",
    "status": "PAID",
    "amount": "500000.00",
    "currency": "IDR",
    "paymentMethod": "CC",
    "bank": "CIMB",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T10:40:00Z"
  }
}

4. WeChat Pay

Payment Method: WECHATPAY

WeChat Pay is processed as a QR payment method. The callback structure is similar to QRIS but with payment method set to WECHATPAY.

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440003",
    "partnerReferenceNo": "ORDER-123459",
    "status": "PAID",
    "amount": "300000.00",
    "currency": "IDR",
    "paymentMethod": "WECHATPAY",
    "bank": "WECHATPAY",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T10:35:00Z"
  }
}

5. Alipay

Payment Method: ALIPAY

Alipay is also processed as a QR payment method, similar to WeChat Pay.

json
{
  "timestamp": "2026-01-04T10:30:00Z",
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440004",
    "partnerReferenceNo": "ORDER-123460",
    "status": "PAID",
    "amount": "400000.00",
    "currency": "IDR",
    "paymentMethod": "ALIPAY",
    "bank": "ALIPAY",
    "paidAt": "2026-01-04T10:30:00Z",
    "expiredAt": "2026-01-04T10:35:00Z"
  }
}

Status Values

PAID
Transaction has been paid successfully
EXPIRED
Transaction has expired before payment was received
FAILED
Transaction failed due to an error or rejection
PENDING
Transaction is awaiting payment (e.g. VA waiting for transfer)

Retry Mechanism

If the callback fails (non-2xx response), the system will retry up to 10 times with exponential backoff. Failed callbacks can be queried via the callback log API.

Security

  • All callbacks are signed with HMAC-SHA256 using the business unit's secret API key
  • Merchants should verify the X-Signature header to ensure callback authenticity
  • Callbacks are sent over HTTPS only

Signature Verification Example

The secret used for signature verification is the Private Key from your business unit's API credentials. The Private Key has the format pk_xxxxxxxxxxxxxxxxx (starts with pk_ prefix).

Go

go
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

func verifyCallbackSignature(payload []byte, signature string, secret string) bool {
    h := hmac.New(sha256.New, []byte(secret))
    h.Write(payload)
    expectedSignature := hex.EncodeToString(h.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

PHP

php
<?php

/**
 * Verify callback signature using HMAC-SHA256
 *
 * @param string $payload   The raw request body
 * @param string $signature The X-Signature header value
 * @param string $secret    The business unit's Private Key (format: pk_xxxxxxxxxxxxxxxxx)
 * @return bool True if signature is valid
 */
function verifyCallbackSignature(string $payload, string $signature, string $secret): bool
{
    $expectedSignature = hash_hmac('sha256', $payload, $secret);
    return hash_equals($expectedSignature, $signature);
}

// Example usage:
// $payload   = file_get_contents('php://input');
// $signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// $secret    = 'pk_xxxxxxxxxxxxxxxxx'; // Your Private Key
// $isValid   = verifyCallbackSignature($payload, $signature, $secret);
?>