/sandbox/v1/transaction/create
| Header | Wajib? | Deskripsi | Contoh |
|---|---|---|---|
X-Yobase-Key | Wajib | API Key proyek Sandbox/Production | sbx_a1b2c3d4... |
X-Yobase-Timestamp | Wajib | UNIX timestamp detik saat request dikirim | 1794666108 |
X-Yobase-Signature | Wajib | HMAC-SHA256 signature request | 6a867d879aef... |
Idempotency-Key | Opsional | UUID unik mencegah double invoice saat retry jaringan | uuid-idem-001 |
{
"merchant_order_id": "INV-20261112-001",
"amount": 100000,
"payment_method": "QRIS",
"customer_name": "Ahmad Santoso",
"customer_email": "[email protected]",
"customer_phone": "081234567890",
"return_url": "https://tokoanda.com/checkout/sukses",
"expired_minutes": 60
}
{
"success": true,
"message": "Transaksi berhasil dibuat.",
"data": {
"merchant_order_id": "INV-20261112-001",
"yobase_reference": "PAY-8F92A1B0C4892A",
"payment_method": "QRIS",
"base_amount": 100000,
"fee_amount": 1200,
"gross_amount": 101200,
"status": "PENDING",
"pay_url": "https://app.yobasepay.net/pay/PAY-8F92A1B0C4892A",
"qr_string": "00020101021226670016ID.CO.QRIS.WWW...",
"va_number": null,
"expired_at": "2026-11-14 22:21:48"
}
}
/sandbox/v1/transaction/status
{
"merchant_order_id": "INV-20261112-001"
}
{
"success": true,
"data": {
"merchant_order_id": "INV-20261112-001",
"yobase_reference": "PAY-8F92A1B0C4892A",
"payment_method": "QRIS",
"base_amount": 100000,
"fee_amount": 1200,
"gross_amount": 101200,
"status": "SUCCESS",
"pay_url": "https://app.yobasepay.net/pay/PAY-8F92A1B0C4892A",
"paid_at": "2026-11-14 21:21:48",
"expired_at": "2026-11-14 22:21:48"
}
}
Webhook Callback ke URL Server Merchant
X-Yobase-Signature: f8e9a1b2c3d4e5f67a8b9c0d1e2f3a4b... X-Yobase-Timestamp: 1794666108Content-Type: application/json User-Agent: YobasePay-Webhook-Engine/1.0
{
"event": "transaction.paid",
"environment": "PRODUCTION",
"project_slug": "toko-anda",
"data": {
"merchant_order_id": "INV-20261112-001",
"yobase_reference": "PAY-8F92A1B0C4892A",
"payment_method": "QRIS",
"base_amount": 100000,
"fee_amount": 1200,
"gross_amount": 101200,
"status": "SUCCESS",
"customer_name": "Ahmad Santoso",
"customer_email": "[email protected]",
"customer_phone": "081234567890",
"paid_at": "2026-11-14 21:21:48"
},
"timestamp": 1794666108}
HTTP 200 OK dengan body text OK agar sistem kami tidak mengulang pengiriman (retry maksimal 5x).
/sandbox/v1/transaction/simulate
Mengubah status tagihan Sandbox seketika menjadi SUCCESS atau FAILED dan otomatis mentrigger pengiriman webhook callback ke server Anda untuk automated testing.
{
"merchant_order_id": "INV-20261112-001",
"action": "SUCCESS"
}
X-Yobase-Key
sbx_ untuk Sandbox atau live_ untuk Production).X-Yobase-Timestamp
X-Yobase-Signature
Idempotency-Key
stringToSign = METHOD + ":" + PATH + ":" + TIMESTAMP + ":" + LOWERCASE(SHA256(RAW_JSON_BODY))
signature = hash_hmac("sha256", stringToSign, SECRET_KEY)
| HTTP | Kode Error | Keterangan |
|---|---|---|
| 400 | GATEWAY_REJECTED |
Parameter ditolak atau kanal pembayaran nonaktif. |
| 401 | INVALID_SIGNATURE |
Signature HMAC salah atau timestamp selisih > 300 detik. |
| 403 | IP_NOT_WHITELISTED |
IP server tidak terdaftar pada IP Whitelist proyek. |
| 404 | NOT_FOUND |
Data tagihan / transaksi tidak ditemukan. |
| 409 | DUPLICATE_ORDER_ID |
merchant_order_id sudah pernah digunakan. |
| 422 | VALIDATION_ERROR |
Format payload JSON tidak memenuhi validasi sistem. |
| 429 | RATE_LIMIT_EXCEEDED |
Melebihi batas permintaan (60 request/menit). |
HTTP 200 OK agar tidak memicu pengiriman ulang (maks 5x retry).
<?php
$secretKey = 'sec_kunci_rahasia_proyek_anda';
$signature = $_SERVER['HTTP_X_YOBASE_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_YOBASE_TIMESTAMP'] ?? '';
$rawBody = file_get_contents('php://input');
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(401); exit("Expired timestamp");
}
$bodyHash = strtolower(hash('sha256', $rawBody));
$stringToSign = "POST:" . $_SERVER['REQUEST_URI'] . ":{$timestamp}:{$bodyHash}";
$expectedSign = hash_hmac('sha256', $stringToSign, $secretKey);
if (!hash_equals($expectedSign, $signature)) {
http_response_code(401); exit("Invalid signature");
}
$payload = json_decode($rawBody, true);
http_response_code(200);
echo "OK";
?>
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const secretKey = 'sec_kunci_rahasia_proyek_anda';
const signature = req.headers['x-yobase-signature'];
const timestamp = req.headers['x-yobase-timestamp'];
const rawBody = req.body.toString('utf8');
if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp)) > 300) {
return res.status(401).send('Expired timestamp');
}
const bodyHash = crypto.createHash('sha256').update(rawBody).digest('hex');
const stringToSign = `POST:${req.originalUrl}:${timestamp}:${bodyHash}`;
const expected = crypto.createHmac('sha256', secretKey).update(stringToSign).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(rawBody);
res.status(200).send('OK');
});
import hmac, hashlib, time, json
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
secret_key = b"sec_kunci_rahasia_proyek_anda"
signature = request.headers.get("X-Yobase-Signature", "")
timestamp = request.headers.get("X-Yobase-Timestamp", "")
raw_body = request.get_data()
if abs(int(time.time()) - int(timestamp)) > 300:
abort(401, "Expired timestamp")
body_hash = hashlib.sha256(raw_body).hexdigest().lower()
string_to_sign = f"POST:{request.path}:{timestamp}:{body_hash}"
expected = hmac.new(secret_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, "Invalid signature")
payload = json.loads(raw_body)
return "OK", 200
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"math"
"net/http"
"strconv"
"strings"
"time"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
secretKey := "sec_kunci_rahasia_proyek_anda"
signature := r.Header.Get("X-Yobase-Signature")
timestampStr := r.Header.Get("X-Yobase-Timestamp")
body, _ := io.ReadAll(r.Body)
ts, _ := strconv.ParseInt(timestampStr, 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
http.Error(w, "Expired timestamp", http.StatusUnauthorized)
return
}
bodyHash := sha256.Sum256(body)
bodyHashHex := strings.ToLower(hex.EncodeToString(bodyHash[:]))
stringToSign := "POST:" + r.URL.Path + ":" + timestampStr + ":" + bodyHashHex
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(stringToSign))
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}