This guide walks you through making your first payment in four steps.
Prerequisites
- Your Client Key (
X-PARTNER-ID) — provided during onboarding - Your RSA private key — used to sign every request (see Signature Generation)
- Your base URL — provided during onboarding
Step 1: Generate Your RSA Key Pair
Generate a 2048-bit RSA key pair. Share the public key during onboarding; keep the private key secret.
# Generate private key
openssl genrsa -out private_key.pem 2048
# Extract public key
openssl rsa -in private_key.pem -pubout -out public_key.pem
Step 2: Build the Request Headers
Every API request requires three headers:
| Header | Value |
|---|---|
| X-PARTNER-ID | Your client key |
| X-TIMESTAMP | Current time in ISO 8601 (e.g., 2025-01-15T14:30:00+07:00) |
| X-SIGNATURE | RSA-SHA256 signature (see below) |
The signature is generated from: {METHOD}:{URL}:{SHA256_OF_BODY}:{TIMESTAMP}
See Signature Generation for the full algorithm and code examples.
Step 3: Create a QRIS Payment
Make a POST request to /api/create/qris:
# Variables — replace with your actual values
CLIENT_KEY="your-client-key"
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # Current time, ISO 8601 with offset
SIGNATURE="your-generated-signature"
BASE_URL="https://your-assigned-base-url.com"
curl -X POST "${BASE_URL}/api/create/qris" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-PARTNER-ID: ${CLIENT_KEY}" \
-H "X-TIMESTAMP: ${TIMESTAMP}" \
-H "X-SIGNATURE: ${SIGNATURE}" \
-d '{
"merchant_transaction_id": "ORDER-001",
"amount": "50000",
"currency": "IDR",
"customer_name": "John Doe",
"product_name": "Test Order"
}'
const crypto = require('crypto');
const fs = require('fs');
const https = require('https');
const CLIENT_KEY = 'your-client-key';
const BASE_URL = 'https://your-assigned-base-url.com';
const ENDPOINT = '/api/create/qris';
const timestamp = new Date().toISOString();
const body = JSON.stringify({
merchant_transaction_id: 'ORDER-001',
amount: '50000',
currency: 'IDR',
customer_name: 'John Doe',
product_name: 'Test Order'
});
// Generate signature
const payloadHash = crypto.createHash('sha256').update(body).digest('hex').toLowerCase();
const stringToSign = `POST:${ENDPOINT}:${payloadHash}:${timestamp}`;
const privateKey = fs.readFileSync('./private_key.pem');
const sign = crypto.createSign('RSA-SHA256');
sign.update(stringToSign);
const signature = sign.sign(privateKey, 'base64');
// Make request
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-PARTNER-ID': CLIENT_KEY,
'X-TIMESTAMP': timestamp,
'X-SIGNATURE': signature
}
};
const req = https.request(`${BASE_URL}${ENDPOINT}`, options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log(JSON.parse(data)));
});
req.write(body);
req.end();
<?php
$clientKey = 'your-client-key';
$baseUrl = 'https://your-assigned-base-url.com';
$endpoint = '/api/create/qris';
$timestamp = date('c'); // ISO 8601
$body = json_encode([
'merchant_transaction_id' => 'ORDER-001',
'amount' => '50000',
'currency' => 'IDR',
'customer_name' => 'John Doe',
'product_name' => 'Test Order',
]);
// Generate signature
$payloadHash = strtolower(bin2hex(hash('sha256', $body, true)));
$stringToSign = "POST:{$endpoint}:{$payloadHash}:{$timestamp}";
$privateKey = openssl_pkey_get_private('file://./private_key.pem');
openssl_sign($stringToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signatureB64 = base64_encode($signature);
// Make request
$ch = curl_init("{$baseUrl}{$endpoint}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
"X-PARTNER-ID: {$clientKey}",
"X-TIMESTAMP: {$timestamp}",
"X-SIGNATURE: {$signatureB64}",
],
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Success Response:
{
"status": true,
"message": "success",
"data": {
"transaction_id": "8366a675-f1cc-418c-b3a8-358410671ca0",
"qr_url": "{host}/images/payment/qris/BP1f44d20caefb8b0ef181797bffed088c.png",
"qr_content": "00020101021226660014ID.EXAMPLE.WWW01189360000000000000000215ID12345678901230303UMI51440014ID.CO.QRIS.WWW0215ID12345678901230303UMI5204599953033605405100005802ID5913EXAMPLE STORE6007JAKARTA61051234562230703A010812qris_invoice6304FF2D",
"redirect_url": "{host}/checkout/8366a675-f1cc-418c-b3a8-358410671ca0/qris",
"expired_at": "2025-01-15T15:00:00.000000Z"
}
}
Display the QR code from data.qr_url or redirect the customer to data.redirect_url.
Step 4: Receive the Webhook
When the customer completes payment, the gateway sends a payment.v2.completed webhook to your registered endpoint:
{
"event": "payment.v2.completed",
"timestamp": "2025-01-15T14:35:00+07:00",
"data": {
"transaction_id": "8366a675-f1cc-418c-b3a8-358410671ca0",
"merchant_transaction_id": "ORDER-001",
"amount": "50000.00",
"currency": "IDR",
"channel": "QRIS",
"status": "success",
"payment_at": "2025-01-15T14:34:58+07:00",
"customer_info": {
"customer_name": "John Doe",
"product_name": "Test Order"
}
}
}
Your endpoint must:
- Verify the signature — see Signature Verification
- Return HTTP 200 within 5 seconds
- Implement idempotency — use
data.transaction_idto detect duplicate deliveries
What's Next?
| Goal | Page |
|---|---|
| Accept payments via hosted checkout page | Checkout Invoice |
| Accept payments via VA, bank, or e-wallet | Generate Direct Payment |
| Send payouts / disbursements | Payout |
| Handle all webhook events | Webhook V2 |
| Verify incoming webhook signatures | Signature Verification |
| Understand the full integration flow | Integration Flows |