YobasePay API v1.0
Simulator Masuk Daftar
Spesifikasi Payload Request & Respon
Gunakan panduan format JSON di bawah untuk memanggil REST API YobasePay secara presisi.
Buka Simulator API
POST /sandbox/v1/transaction/create
Buat Tagihan QRIS / Virtual Account
Header Request Wajib:
HeaderWajib?DeskripsiContoh
X-Yobase-KeyWajibAPI Key proyek Sandbox/Productionsbx_a1b2c3d4...
X-Yobase-TimestampWajibUNIX timestamp detik saat request dikirim1794666108
X-Yobase-SignatureWajibHMAC-SHA256 signature request6a867d879aef...
Idempotency-KeyOpsionalUUID unik mencegah double invoice saat retry jaringanuuid-idem-001
REQUEST BODY (JSON)
{
  "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
}
RESPON SUKSES (HTTP 201 CREATED)
{
  "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"
  }
}
POST /sandbox/v1/transaction/status
Cek Status Tagihan Real-Time
REQUEST BODY (JSON)
{
  "merchant_order_id": "INV-20261112-001"
}
RESPON SUKSES (HTTP 200 OK)
{
  "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"
  }
}
POST Webhook Callback ke URL Server Merchant
Notifikasi Real-time Pembayaran
HEADER CALLBACK DARI YOBASEPAY
X-Yobase-Signature: f8e9a1b2c3d4e5f67a8b9c0d1e2f3a4b...
X-Yobase-Timestamp: 1794666108Content-Type: application/json
User-Agent: YobasePay-Webhook-Engine/1.0
PAYLOAD NOTIFIKASI WEBHOOK (JSON)
{
  "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}
Server Anda wajib mengembalikan respon HTTP 200 OK dengan body text OK agar sistem kami tidak mengulang pengiriman (retry maksimal 5x).
POST /sandbox/v1/transaction/simulate
Khusus Sandbox

Mengubah status tagihan Sandbox seketika menjadi SUCCESS atau FAILED dan otomatis mentrigger pengiriman webhook callback ke server Anda untuk automated testing.

REQUEST SIMULASI (JSON)
{
  "merchant_order_id": "INV-20261112-001",
  "action": "SUCCESS"
}
1. Header Autentikasi Wajib
  • X-Yobase-Key
    API Key proyek (awalan sbx_ untuk Sandbox atau live_ untuk Production).
  • X-Yobase-Timestamp
    UNIX timestamp detik saat request dikirim (toleransi maksimal 300 detik).
  • X-Yobase-Signature
    Tanda tangan digital HMAC-SHA256 menggunakan Secret Key proyek.
  • Idempotency-Key
    String unik / UUID opsional untuk mencegah transaksi ganda akibat retry jaringan.
2. Rumus Signature HMAC-SHA256
stringToSign = METHOD + ":" + PATH + ":" + TIMESTAMP + ":" + LOWERCASE(SHA256(RAW_JSON_BODY))
signature    = hash_hmac("sha256", stringToSign, SECRET_KEY)
3. Kode Error Standar API
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).
4. Contoh Kode Verifikasi Webhook di Server Anda
Server Anda wajib mengembalikan 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"))
}