How do I create my first payment through the API?
Six steps: get your keys, choose an access mode, set your webhook URL, enable methods, create a payment in sandbox, then verify the webhook and go live. Sandbox and production share the same keys and URLs, so going live changes nothing but the account mode.
This page was checked against the API code as of October 16, 2026.
What do I need before writing code?#
- A merchant account that has passed verification (KYC). Until then,
POST /paymentreturns403 MERCHANT_KYC_INCOMPLETE. - A server reachable from the internet to receive webhooks. Internal addresses such as
localhostor private IPs are rejected ascallback_url. - Access to the merchant dashboard to copy your keys and set URLs.
In what order should I integrate?#
Get your API key and secret key
Dashboard, API Settings, then API Keys. The secret key is shown after PIN verification. Keep it in a server environment variable, never in client code or a repository.
Choose an access mode
API Settings, Mode & access. Fixed-IP server: register its IP (IP Whitelist). Serverless: turn the whitelist off to use Enhanced HMAC. The differences are in Authentication.
Set the webhook and return URLs
API Settings, Webhook & Return URL. Both can be overridden per payment with the
callback_urlandreturn_urlfields.Enable payment methods
Payment methods in the dashboard. Only methods enabled on your account can be used, and
GET /payment-methodsreturns exactly that list with eachpayment_code.Turn on sandbox and create a payment
Sandbox mode is under Mode & access. Send the request below, open the
payment_linkfrom the response, then click simulate success.Verify the webhook, then turn sandbox off
Make sure your endpoint verifies
X-Signatureand answers 2xx. Then turn sandbox off; keys and URLs stay the same.
What does the first request look like?#
This example is for IP Whitelist mode: only POST requests are signed, with HMAC-SHA256 over the JSON body exactly as sent. For Enhanced HMAC, see its example.
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();{
"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
}
}What must I check before going live?#
- Webhooks are verified with an HMAC over the raw body and compared in constant time (how).
- Orders are marked paid only from the webhook or a status check, never from parameters on
return_url. - Webhook processing is idempotent, because the same webhook can arrive more than once.
- The webhook
amountis matched against the order before goods are delivered. - Every order has a unique
merchant_ref_id, and retries after a timeout reuse the same ref (idempotency). - Both
paidandcompletedare treated as paid (why). 429responses are handled by waiting for theRetry-Afterheader.- Sandbox mode is turned off.
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
