Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Signature Validation

Signature Validation adalah lapisan keamanan tambahan menggunakan HMAC-SHA256 untuk memverifikasi integritas dan keaslian setiap request. Fitur ini opsional dan dapat diaktifkan per merchant oleh admin.

Cara Kerja

1. timestamp      = unix timestamp saat ini (integer, dalam detik)
2. body_string    = JSON string persis dari request body
3. string_to_sign = body_string + ":" + timestamp
4. signature      = Base64( HMAC-SHA256( secret_key, string_to_sign ) )

Catatan penting: body_string harus identik byte-for-byte dengan body yang dikirim dalam request. Jangan pretty-print atau ubah urutan field JSON.

Signature Secret

secret_key adalah nilai terpisah dari X-API-Key. Nilai ini:

  • Tidak ditampilkan di CMS
  • Diberikan langsung oleh tim support saat aktivasi fitur Signature Validation
  • Harus disimpan dengan aman dan tidak pernah di-commit ke repositori

Validasi Timestamp

Sistem menolak request jika:

  • Timestamp lebih dari 300 detik (5 menit) di masa lalu
  • Timestamp lebih dari 60 detik di masa depan

Hal ini mencegah replay attack.

Implementasi per Bahasa

PHP

<?php
$secretKey  = "signature_secret_dari_support";
$body       = ['product_id' => 'XL5', 'cust_id' => '08123456789', 'req_id' => 'TXN-001'];
$bodyString = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$timestamp  = time();
$toSign     = $bodyString . ":" . $timestamp;

$signature = base64_encode(hash_hmac('sha256', $toSign, $secretKey, true));

// Kirim dalam header
// X-Signature: $signature
// X-Timestamp: $timestamp
?>

Node.js

const crypto = require('crypto');

const secretKey  = 'signature_secret_dari_support';
const body       = { product_id: 'XL5', cust_id: '08123456789', req_id: 'TXN-001' };
const bodyString = JSON.stringify(body); // urutan key harus sama dengan yang dikirim
const timestamp  = Math.floor(Date.now() / 1000);
const toSign     = `${bodyString}:${timestamp}`;

const signature = crypto
  .createHmac('sha256', secretKey)
  .update(toSign)
  .digest('base64');

Python

import hmac, hashlib, base64, time, json

secret_key  = "signature_secret_dari_support"
body        = {"product_id": "XL5", "cust_id": "08123456789", "req_id": "TXN-001"}
body_string = json.dumps(body, separators=(',', ':'), ensure_ascii=False)
timestamp   = int(time.time())
to_sign     = f"{body_string}:{timestamp}"

signature = base64.b64encode(
    hmac.new(secret_key.encode(), to_sign.encode(), hashlib.sha256).digest()
).decode()

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

String secretKey  = "signature_secret_dari_support";
String bodyString = "{\"product_id\":\"XL5\",\"cust_id\":\"08123456789\",\"req_id\":\"TXN-001\"}";
long   timestamp  = System.currentTimeMillis() / 1000;
String toSign     = bodyString + ":" + timestamp;

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256"));
String signature = Base64.getEncoder().encodeToString(mac.doFinal(toSign.getBytes("UTF-8")));

Go

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "time"
)

secretKey := "signature_secret_dari_support"
body := map[string]string{
    "product_id": "XL5",
    "cust_id":    "08123456789",
    "req_id":     "TXN-001",
}
bodyBytes, _ := json.Marshal(body)
timestamp := time.Now().Unix()
toSign    := fmt.Sprintf("%s:%d", string(bodyBytes), timestamp)

mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(toSign))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

Rust

#![allow(unused)]
fn main() {
use hmac::{Hmac, Mac};
use sha2::Sha256;
use base64::{engine::general_purpose, Engine};
use std::time::{SystemTime, UNIX_EPOCH};

type HmacSha256 = Hmac<Sha256>;

let secret_key  = "signature_secret_dari_support";
let body_string = r#"{"product_id":"XL5","cust_id":"08123456789","req_id":"TXN-001"}"#;
let timestamp   = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_secs();
let to_sign = format!("{}:{}", body_string, timestamp);

let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes()).unwrap();
mac.update(to_sign.as_bytes());
let signature = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
}

Tambahkan ke Cargo.toml: hmac = "0.12", sha2 = "0.10", base64 = "0.22"

Contoh Request dengan Signature

POST /api/v1/merchant/disbursement/payment HTTP/1.1
Host: api-sandbox.alfakios.com
Content-Type: application/json
X-API-Key: apk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Signature: uW8tV+R6zKpN1234ABCDabcd==
X-Timestamp: 1714435200

{"req_id":"PAY-001","product_id":"DSTF","account_number":"1234567890","amount":"100000"}

Response Error Signature

{
  "success": false,
  "message": "Invalid signature",
  "error": "Signature validation failed"
}

HTTP Status: 401 Unauthorized