How do I create a payment?
Send POST /payment with the amount, method code, and buyer details. The 201 Created response contains the invoice, a payment_link to the SekaliPay payment page, and method-specific payment details. The payment is valid for 30 minutes.
This page was checked against the API code as of October 16, 2026.
Where do I send the request?#
/api/v1/gateway/payment- Limit per minute:
- 5 requests per minute per API key
- Signature:
- Required in both modes
Which fields do I send?#
merchant_ref_idstringRequired- Order ID from your system, up to 191 characters, unique per account (another account may use the same ref). Stick to letters, digits,
-, and_so it is safe in the status-check URL. amountintegerRequired- The amount you receive, in rupiah. At least 100 and within the method's
min_amountandmax_amount. payment_codestringRequired- Method code from
GET /payment-methods. The method must be enabled on your account. customer_namestringRequired- Buyer name, up to 100 characters.
customer_emailstringRequired- A valid buyer email, up to 100 characters.
customer_phonestringRequired- Buyer phone number, up to 20 characters.
callback_urlstringOptional- Webhook URL for this payment only, up to 500 characters. Must be a public address;
localhostand private IPs are rejected. Empty means the dashboard URL. return_urlstringOptional- Where the buyer's browser goes after paying, up to 500 characters. Empty means the dashboard URL. See Buyer redirects.
metadataobjectOptional- Free-form data for your own use. Stored with the transaction, but not returned in API responses or webhooks.
How much does the buyer pay?#
amount is what you receive. The method fee is added on top, so the buyer pays total = amount + fee. In the example below you receive Rp 150,000 and the buyer pays Rp 151,500. Fees per method are on the fees page.
What does a request look like?#
BODY='{"merchant_ref_id":"ORDER-2026-00184","amount":150000,"payment_code":"QRIS","customer_name":"Budi Santoso","customer_email":"[email protected]","customer_phone":"081234567890","callback_url":"https://toko-anda.id/webhook/sekalipay","return_url":"https://toko-anda.id/checkout/selesai"}'
SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SEKALIPAY_SECRET_KEY" | sed 's/^.* //')
curl -X POST https://sekalipay.com/api/v1/gateway/payment \
-H "X-API-Key: $SEKALIPAY_API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
--data-raw "$BODY"$body = json_encode([
'merchant_ref_id' => 'ORDER-2026-00184',
'amount' => 150000,
'payment_code' => 'QRIS',
'customer_name' => 'Budi Santoso',
'customer_email' => '[email protected]',
'customer_phone' => '081234567890',
'callback_url' => 'https://toko-anda.id/webhook/sekalipay',
'return_url' => 'https://toko-anda.id/checkout/selesai',
]);
$signature = hash_hmac('sha256', $body, getenv('SEKALIPAY_SECRET_KEY'));
$ch = curl_init('https://sekalipay.com/api/v1/gateway/payment');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
'X-API-Key: ' . getenv('SEKALIPAY_API_KEY'),
'X-Signature: ' . $signature,
],
]);
$response = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);import { createHmac } from "node:crypto";
const body = JSON.stringify({
"merchant_ref_id": "ORDER-2026-00184",
"amount": 150000,
"payment_code": "QRIS",
"customer_name": "Budi Santoso",
"customer_email": "[email protected]",
"customer_phone": "081234567890",
"callback_url": "https://toko-anda.id/webhook/sekalipay",
"return_url": "https://toko-anda.id/checkout/selesai"
});
const signature = createHmac("sha256", process.env.SEKALIPAY_SECRET_KEY)
.update(body)
.digest("hex");
const res = await fetch("https://sekalipay.com/api/v1/gateway/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-API-Key": process.env.SEKALIPAY_API_KEY,
"X-Signature": signature,
},
body,
});
const result = await res.json();The examples above are for IP Whitelist mode. For Enhanced HMAC, use the helper in Authentication.
What does a successful response contain?#
{
"status": true,
"message": "OK",
"data": {
"merchant_ref_id": "ORDER-2026-00184",
"invoice": "SPY1791446400K3QZ",
"amount": 150000,
"fee": 1500,
"total": 151500,
"payment_code": "QRIS",
"payment_link": "https://sekalipay.com/payment/SPY1791446400K3QZ",
"qr_link": "https://sekalipay.com/payment/SPY1791446400K3QZ/qris",
"virtual_account": null,
"checkout_url": null,
"payment_guide": null,
"expired_at": "2026-10-08T08:30:00.000000Z",
"status": "pending",
"is_sandbox": false
}
}invoicestring- SekaliPay invoice number, e.g.
SPY1791446400K3QZ. Keep it for reconciliation and support requests. amountinteger- The amount you receive.
feeinteger- Transaction fee, paid by the buyer.
totalinteger- What the buyer has to pay.
payment_linkstring- The SekaliPay payment page. Always present; sending the buyer here is the simplest option.
qr_linkstring | null- QR image for QRIS methods.
virtual_accountstring | null- VA number for virtual account methods.
checkout_urlstring | null- Link to the e-wallet app or page.
payment_guidestring | null- Payment instructions as HTML when the partner provides them. Sanitize before rendering.
expired_atstring- Payment deadline, ISO 8601 UTC, 30 minutes after creation.
statusstring- Always
pendingat creation. is_sandboxbooleantruewhen created in sandbox mode.
Which payment details are filled depends on the method; the rest are null.
How do I retry a request safely?#
merchant_ref_id is the idempotency key. If a request times out and you do not know the outcome, send it again with the same ref. If the payment already exists, you get 409 PAYMENT_ALREADY_EXISTS with that payment's data instead of a second payment.
{
"status": false,
"message": "PAYMENT_ALREADY_EXISTS",
"data": {
"merchant_ref_id": "ORDER-2026-00184",
"invoice": "SPY1791446400K3QZ",
"amount": 150000,
"fee": 1500,
"total": 151500,
"payment_code": "QRIS",
"payment_link": "https://sekalipay.com/payment/SPY1791446400K3QZ",
"qr_link": "https://sekalipay.com/payment/SPY1791446400K3QZ/qris",
"expired_at": "2026-10-08T08:30:00.000000Z",
"status": "pending"
}
}Requests rejected with any other error (4xx other than 409, or 5xx) store nothing, so the same ref can be used again.
Which errors are specific to this endpoint?#
| HTTP | Code | Meaning |
|---|---|---|
| 403 | MERCHANT_KYC_INCOMPLETE | Merchant verification is incomplete. data.onboarding_url points to the page to complete it. |
| 400 | PAYMENT_METHOD_NOT_AVAILABLE | Unknown method code, or the method is currently disabled. |
| 403 | PAYMENT_METHOD_NOT_ENABLED | The method exists but is not enabled on your account. |
| 400 | AMOUNT_OUT_OF_RANGE | The amount is outside the method's range; data holds min_amount and max_amount. |
| 409 | PAYMENT_ALREADY_EXISTS | This merchant_ref_id already has a payment; data contains it. |
| 409 | DUPLICATE_REF_ID | Two requests with the same ref arrived at the same time and this one lost. Check that ref's status. |
| 500 | PAYMENT_GATEWAY_ERROR | The payment partner rejected the request or did not answer. Nothing was stored; retry with the same ref or another method. |
| 500 | INTERNAL_SERVER_ERROR | An error on our side; details are not sent. Retry, or contact support with the time and merchant_ref_id. |
Plus 422 validation errors and authentication errors; see Error codes.
Ready to use your own keys?
Sign in to the merchant dashboard to copy your API key and secret key, set the access mode and webhook URL, and download a Postman collection with your account's keys.
Sign in to the dashboard
