🔒 Changelog v1.1 — Security Update

29 Mei 2026 — Perubahan penting yang mempengaruhi integrasi webhook merchant.

⚠️ Breaking Change: Webhook

  • 1.Webhook sekarang hanya dikirim ke merchant yang sudah mengkonfigurasi Webhook Secret di Developer Settings.
  • 2.Setiap webhook menyertakan header X-ArtaPay-Signature (HMAC-SHA256, hex). Merchant sangat disarankan memverifikasi signature ini.

Yang Perlu Dilakukan Merchant:

1Buka Developer Settings di dashboard → klik Regenerate Webhook Secret
2Simpan Webhook Secret sebagai environment variable di backend Anda
3Implementasikan verifikasi signature di endpoint webhook Anda (lihat contoh di bagian Verifikasi Signature)

Perubahan Lainnya:

  • • Peningkatan stabilitas pembuatan nomor invoice pada traffic tinggi
  • • Optimasi koneksi database untuk serverless environment
  • • Peningkatan error handling pada proses pembayaran

Timeline Migrasi

TanggalAksi
29 Mei 2026Update dirilis. Webhook tetap dikirim ke merchant yang sudah punya Webhook Secret.
29 Mei – 30 JunPeriode transisi. Merchant disarankan mengimplementasikan verifikasi signature.
1 Juli 2026Webhook hanya dikirim ke merchant dengan Webhook Secret terkonfigurasi.

Pengenalan

ArtaPay API memungkinkan Anda membuat invoice pembayaran, mengecek status, dan menerima notifikasi webhook secara real-time.

Base URL untuk semua request API:

Base URL
https://artapay.michaelk.fun/api/v1

Semua request dan response menggunakan format application/json.

Autentikasi

Setiap request ke API v1 harus menyertakan API key dan signature di header.

Header yang diperlukan:

ParameterTipeWajibKeterangan
X-ArtaPay-Api-KeystringYaAPI key merchant Anda (format: artapay_test_xxx)
X-ArtaPay-SecretstringYaSecret key merchant Anda (sk_test_xxx)
X-ArtaPay-TimestampnumberYaUnix timestamp dalam detik (toleransi ±5 menit)
X-ArtaPay-SignaturestringYaHMAC-SHA256 signature (lihat bagian Signature)
X-Idempotency-KeystringTidakKey unik untuk mencegah duplikasi request (TTL 24 jam)
⚠️
API key dan Secret key hanya ditampilkan sekali saat merchant diapprove. Simpan dengan aman — jika hilang, gunakan fitur Regenerate di dashboard.

Buat Invoice

POST/api/v1/payments

Membuat invoice pembayaran baru. Mengembalikan payment URL yang bisa diberikan ke customer.

Request Body

ParameterTipeWajibKeterangan
amountintegerYaJumlah pembayaran dalam IDR (satuan rupiah)
customerNamestringYaNama customer (maks 100 karakter)
customerEmailstringTidakEmail customer
customerPhonestringTidakNomor telepon customer (format Indonesia)
merchantReferencestringTidakReferensi order dari sistem Anda (maks 100 karakter)
descriptionstringTidakDeskripsi pembayaran (maks 500 karakter)
expiredMinutesintegerTidakDurasi kadaluarsa dalam menit (default: 60, maks: 43200)
currencystringTidakKode mata uang (default: IDR)

Contoh Request

cURL
curl -X POST https://artapay.michaelk.fun/api/v1/payments \
  -H "Content-Type: application/json" \
  -H "X-ArtaPay-Api-Key: artapay_test_abc123" \
  -H "X-ArtaPay-Secret: sk_test_xyz789" \
  -H "X-ArtaPay-Timestamp: 1717123456" \
  -H "X-ArtaPay-Signature: a1b2c3d4..." \
  -H "X-Idempotency-Key: order-001" \
  -d '{
    "amount": 150000,
    "customerName": "Budi Santoso",
    "customerEmail": "budi@email.com",
    "merchantReference": "ORDER-001",
    "expiredMinutes": 60
  }'

Response

201 Created
{
  "success": true,
  "data": {
    "invoiceId": "clx1abc123def456",
    "invoiceNumber": "INV-20250528-0001",
    "paymentUrl": "https://arta-pay.vercel.app/pay/clx1abc123def456",
    "amount": 150000,
    "feeAmount": 2250,
    "netAmount": 147750,
    "currency": "IDR",
    "status": "WAITING_PAYMENT",
    "expiredAt": "2025-05-28T13:00:00.000Z",
    "createdAt": "2025-05-28T12:00:00.000Z"
  }
}

Cek Status Invoice

GET/api/v1/payments/:invoiceId

Mengecek status invoice berdasarkan invoice ID. Lazy expiration dijalankan otomatis.

cURL
curl https://artapay.michaelk.fun/api/v1/payments/clx1abc123def456 \
  -H "X-ArtaPay-Api-Key: artapay_test_abc123" \
  -H "X-ArtaPay-Secret: sk_test_xyz789" \
  -H "X-ArtaPay-Timestamp: 1717123456" \
  -H "X-ArtaPay-Signature: a1b2c3d4..."
200 OK
{
  "success": true,
  "data": {
    "invoiceId": "clx1abc123def456",
    "invoiceNumber": "INV-20250528-0001",
    "status": "PAID",
    "amount": 150000,
    "paidAt": "2025-05-28T12:30:00.000Z"
  }
}

Cek Invoice by Referensi

GET/api/v1/payments/by-reference/:merchantReference

Mencari invoice berdasarkan referensi order dari sistem Anda.

cURL
curl https://artapay.michaelk.fun/api/v1/payments/by-reference/ORDER-001 \
  -H "X-ArtaPay-Api-Key: artapay_test_abc123" \
  -H "X-ArtaPay-Secret: sk_test_xyz789" \
  -H "X-ArtaPay-Timestamp: 1717123456" \
  -H "X-ArtaPay-Signature: a1b2c3d4..."

Batalkan Invoice

POST/api/v1/payments/:invoiceId/cancel

Membatalkan invoice yang masih berstatus WAITING_PAYMENT.

⚠️
Invoice yang sudah PAID, EXPIRED, atau CANCELLED tidak bisa dibatalkan.
cURL
curl -X POST https://artapay.michaelk.fun/api/v1/payments/clx1abc123def456/cancel \
  -H "X-ArtaPay-Api-Key: artapay_test_abc123" \
  -H "X-ArtaPay-Secret: sk_test_xyz789" \
  -H "X-ArtaPay-Timestamp: 1717123456" \
  -H "X-ArtaPay-Signature: a1b2c3d4..."

Webhook Events

ArtaPay mengirim HTTP POST ke webhook URL Anda setelah event pembayaran terjadi.

⚠️
Update v1.1 (29 Mei 2026): Webhook sekarang menyertakan header X-ArtaPay-Signature untuk verifikasi keamanan. Merchant wajib mengkonfigurasi Webhook Secret di Developer Settings agar webhook terkirim. Lihat bagian Verifikasi Signature untuk detail implementasi.

Event yang tersedia:

payment.paidInvoice berhasil dibayar
payment.createdInvoice baru dibuat
payment.failedPembayaran gagal
payment.expiredInvoice kadaluarsa

Contoh Payload

POST ke webhook URL Anda
{
  "event": "payment.paid",
  "invoiceId": "clx1abc123def456",
  "invoiceNumber": "INV-20250528-0001",
  "merchantReference": "ORDER-001",
  "amount": 150000,
  "feeAmount": 2250,
  "netAmount": 147750,
  "currency": "IDR",
  "status": "PAID",
  "customerName": "Budi Santoso",
  "customerEmail": "budi@email.com",
  "paidAt": "2025-05-28T12:30:00.000Z",
  "timestamp": 1717123456
}

Header yang dikirim bersama webhook:

X-ArtaPay-Signature: a1b2c3d4e5f6...  (HMAC-SHA256 hex)
X-ArtaPay-Event: payment.paid
X-ArtaPay-Timestamp: 1717123456
💡
Balas dengan HTTP 2xx untuk konfirmasi penerimaan. Jika tidak, webhook akan ditandai FAILED dan bisa di-retry manual dari dashboard.

Verifikasi Signature

Setiap request dan webhook menggunakan HMAC-SHA256 untuk memastikan keaslian data.

Cara Membuat Signature (Request ke ArtaPay)

payload = timestamp + ":" + SHA256(requestBody)
signature = HMAC-SHA256(secretKey, payload)

Contoh JavaScript

signature.js
const crypto = require('crypto');

function createSignature(secretKey, timestamp, body) {
  const bodyHash = crypto
    .createHash('sha256')
    .update(body)
    .digest('hex');
  
  const payload = `${timestamp}:${bodyHash}`;
  
  return crypto
    .createHmac('sha256', secretKey)
    .update(payload)
    .digest('hex');
}

// Penggunaan
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ amount: 150000, customerName: 'Budi' });
const sig = createSignature('sk_test_xyz789', timestamp, body);

Verifikasi Webhook dari ArtaPay

Saat ArtaPay mengirim webhook ke endpoint Anda, gunakan Webhook Secret (dari Developer Settings) untuk memverifikasi header X-ArtaPay-Signature.

signature = HMAC-SHA256(webhookSecret, rawRequestBody)
Format: hex string

Verifikasi Webhook (Node.js / Express)

verify-webhook.js
const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, webhookSecret) {
  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody) // raw body string, BUKAN parsed JSON
    .digest('hex');

  // Gunakan timingSafeEqual untuk mencegah timing attack
  try {
    return crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(signature, 'hex')
    );
  } catch {
    return false;
  }
}

// Di Express handler:
app.post('/webhook/artapay', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString();
  const signature = req.headers['x-artapay-signature'];

  if (!signature || !verifyWebhookSignature(rawBody, signature, process.env.ARTAPAY_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const payload = JSON.parse(rawBody);
  // Proses event...
  res.status(200).json({ received: true });
});

Verifikasi Webhook (PHP)

verify-webhook.php
<?php
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_ARTAPAY_SIGNATURE'] ?? '';
$webhookSecret = getenv('ARTAPAY_WEBHOOK_SECRET');

$expected = hash_hmac('sha256', $rawBody, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit(json_encode(['error' => 'Invalid signature']));
}

$data = json_decode($rawBody, true);
// Proses event...
http_response_code(200);
echo json_encode(['received' => true]);

Verifikasi Webhook (Python / Flask)

verify_webhook.py
import hmac, hashlib, os
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook/artapay', methods=['POST'])
def handle_webhook():
    raw_body = request.get_data()
    signature = request.headers.get('X-ArtaPay-Signature', '')
    secret = os.environ['ARTAPAY_WEBHOOK_SECRET']

    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    payload = request.get_json()
    # Proses event...
    return jsonify({'received': True}), 200
⚠️
PENTING: Pastikan Anda menggunakan raw body string (bukan parsed/re-serialized JSON) saat menghitung signature. Perbedaan whitespace atau urutan key akan menyebabkan signature tidak cocok.
ℹ️
Webhook Secret bisa didapatkan dari menu Developer Settings di dashboard ArtaPay. Jika belum dikonfigurasi, webhook tidak akan dikirim.

Real-time Payment Status (WebSocket)

Menerima update status secara real-time di sisi klien (browser) tanpa polling.

⚠️
PENTING: Fitur WebSocket harus diaktifkan oleh Admin ArtaPay untuk setiap merchant. Anda dapat mengecek status fitur ini di menu Developer Settings.

Endpoint WebSocket

Koneksi WebSocket tidak memerlukan header Authorization atau API Key. Parameter yang dibutuhkan hanya invoiceId.

WSS URL
wss://artapay.michaelk.fun/api/v1/payments/{invoiceId}/ws

Struktur Pesan (Payload)

Contoh Pesan Diterima
{
  "event": "INVOICE_STATUS",
  "invoiceId": "clx1abc123def456",
  "status": "PAID",
  "paidAt": "2025-05-28T12:30:00.000Z"
}

Contoh Implementasi Klien

checkout.js
const invoiceId = "clx1abc123def456";
const ws = new WebSocket(`wss://artapay.michaelk.fun/api/v1/payments/${invoiceId}/ws`);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.event === "INVOICE_STATUS" && data.status === "PAID") {
    alert("Pembayaran berhasil!");
    ws.close();
  }
};

ws.onclose = (event) => {
  if (event.code === 1008) {
    console.warn("Fitur WebSocket belum aktif untuk merchant ini.");
  }
};
ℹ️
Browser tidak melakukan auto-reconnect secara otomatis. Anda disarankan menambahkan fungsi setInterval atau menggunakan library tambahan seperti partysocket.

Web Popup / Modal Checkout

Menampilkan halaman pembayaran (invoice) secara langsung di dalam website Anda menggunakan tampilan overlay/modal.

⚠️
PENTING: Fitur Web Popup harus diaktifkan oleh Admin ArtaPay untuk setiap merchant. Anda dapat mengecek status fitur ini di menu Developer Settings.

Integrasi fitur ini sangat mudah, Anda tidak perlu menginstall library tambahan apa pun. Cukup sisipkan script bawaan ArtaPay ke dalam HTML website Anda.

Langkah 1: Tambahkan Script

<script src="https://arta-pay.vercel.app/artapay.js"></script>

Langkah 2: Panggil Fungsi Checkout

// Anda bisa mendaftarkan fungsi callback ketika pembayaran berhasil (Opsional)
ArtaPay.onSuccess = function(invoiceId) {
  console.log("Hore! Pembayaran berhasil untuk invoice:", invoiceId);
  window.location.href = "/order-success?id=" + invoiceId;
};

// Menampilkan popup pembayaran
// Anda bisa mempassing invoiceId langsung:
ArtaPay.checkout("clx1abc123def456");

// Atau mempassing paymentUrl:
// ArtaPay.checkout("https://arta-pay.vercel.app/pay/clx1abc123def456");

Error Codes

HTTP StatusError CodeKeterangan
400VALIDATION_ERRORRequest body tidak valid atau parameter kurang
400INVALID_JSONRequest body bukan JSON yang valid
400DATE_RANGE_TOO_LARGERentang tanggal melebihi 30 hari
401UNAUTHORIZEDAPI key tidak ada atau tidak valid
401INVALID_SIGNATURESignature tidak cocok atau timestamp kadaluarsa
403FORBIDDENTidak punya akses ke resource ini
403MERCHANT_NOT_ACTIVEAkun merchant tidak aktif
404NOT_FOUNDResource tidak ditemukan
409PAYMENT_IN_PROGRESSPembayaran sedang diproses (double payment)
422INVOICE_EXPIREDInvoice sudah kadaluarsa
422INVOICE_PAIDInvoice sudah dibayar
422INVOICE_CANCELLEDInvoice sudah dibatalkan
422INVALID_STATUSOperasi tidak valid untuk status invoice saat ini
429RATE_LIMIT_EXCEEDEDTerlalu banyak request. Coba lagi nanti.
500INTERNAL_SERVER_ERRORKesalahan internal server

Format Error Response

{
  "success": false,
  "error": {
    "code": "INVALID_SIGNATURE",
    "message": "Signature mismatch"
  }
}