API reference for integrating payments into your application.
Base URL: https://msdzul.com
All endpoints require your per-app API key. Send it as a bearer token on every request.
Authorization
|
Bearer <per-app-api-key>
|
required |
Transaction endpoints for your application. All requests require your per-app API key.
/api/v1/transactions/create
201
Authorization
|
Bearer <per-app-api-key>
|
required |
Content-Type
|
application/json
|
required |
| Name | Type | Description | Required |
|---|---|---|---|
webhook_url
|
string
|
URL to receive webhook when payment completes | required |
order_id
|
string
|
Unique order ID from your system | required |
amount
|
int
|
Payment amount in IDR (e.g. 50000) | required |
payment_method
|
string
|
Payment method (see below) | required |
Payment methods
| Method | Description | Min | Max |
|---|---|---|---|
qris
|
QRIS (QR Code) | 500 | 10.000.000 |
bri_va
|
BRI Virtual Account | 10.000 | 50.000.000 |
bni_va
|
BNI Virtual Account | 10.000 | 50.000.000 |
permata_va
|
Permata Virtual Account | 10.000 | 50.000.000 |
cimb_niaga_va
|
CIMB Niaga Virtual Account | 10.000 | 50.000.000 |
sampoerna_va
|
Sampoerna Virtual Account | 10.000 | 50.000.000 |
bnc_va
|
Bank Neo Commerce Virtual Account | 10.000 | 50.000.000 |
maybank_va
|
Maybank Virtual Account | 10.000 | 50.000.000 |
atm_bersama_va
|
ATM Bersama Virtual Account | 10.000 | 50.000.000 |
artha_graha_va
|
Artha Graha Virtual Account | 10.000 | 50.000.000 |
Amounts outside these limits are rejected with 400 before any call to Pakasir.
Example request
curl -X POST https://msdzul.com/api/v1/transactions/create \
-H "Authorization: Bearer <per-app-api-key>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.com/webhook/pakasir",
"order_id": "INV20260819-001",
"amount": 50000,
"payment_method": "qris"
}'
Success response (201 Created)
{
"txn_id": "vwiqpcriq",
"project": "my-shop",
"order_id": "INV20260819-001",
"amount": 50000,
"fee": 750,
"total_payment": 50750,
"payment_method": "qris",
"qr_string": "00020101021226610016ID.CO.SHOPEE.WWW0118...",
"va_number": "",
"expired_at": "2026-08-20T12:00:00.000Z",
"is_sandbox": true,
"status": "pending",
"completed_at": null
}
The body is the Pakasir API v2 response, forwarded as-is. Read the payment instruction from qr_string (QRIS) or va_number (virtual account) and keep txn_id for cancel and status operations.
Error responses
| Status | Error | Description |
|---|---|---|
400
|
invalid request body
|
Malformed JSON |
400
|
webhook_url is required
|
Missing webhook_url |
400
|
order_id is required
|
Missing order_id |
400
|
amount must be greater than 0
|
Invalid amount |
400
|
amount must be at least ... for payment_method ...
|
Below the method minimum |
400
|
amount must be at most ... for payment_method ...
|
Above the method maximum |
400
|
invalid payment_method
|
Unknown payment method |
401
|
unauthorized
|
Invalid or missing API key |
409
|
transaction already exists
|
Duplicate order_id for this project |
502
|
Pakasir error message | Error from Pakasir (message forwarded) |
500
|
failed to save transaction
|
Database error |
Creating a transaction is idempotent on Pakasir side, so retrying the same order_id with the same body returns the same txn_id.
/api/v1/transactions/cancel
200
Authorization
|
Bearer <per-app-api-key>
|
required |
Content-Type
|
application/json
|
required |
| Name | Type | Description | Required |
|---|---|---|---|
order_id
|
string
|
Order ID to cancel | required |
amount
|
int
|
Deprecated and ignored, Pakasir API v2 cancels by txn_id | — |
Example request
curl -X POST https://msdzul.com/api/v1/transactions/cancel \
-H "Authorization: Bearer <per-app-api-key>" \
-H "Content-Type: application/json" \
-d '{
"order_id": "INV20260819-001"
}'
Success response (200 OK)
{
"status": "cancelled"
}
Error responses
| Status | Error | Description |
|---|---|---|
400
|
invalid request body
|
Malformed JSON |
400
|
order_id is required
|
Missing order_id |
400
|
transaction is already cancelled
|
Already cancelled |
400
|
transaction is already completed
|
Already completed |
400
|
transaction is expired
|
Payment window expired |
401
|
unauthorized
|
Invalid or missing API key |
404
|
transaction not found
|
Transaction not in database |
409
|
transaction was created before the API v2 migration ...
|
No txn_id stored, cancel it in the Pakasir dashboard |
409
|
transaction status changed during cancel
|
A webhook completed the transaction meanwhile |
502
|
Pakasir error message | Error from Pakasir (message forwarded) |
500
|
failed to update transaction status
|
Database error |
/api/v1/transactions/{order_id}
200
Authorization
|
Bearer <per-app-api-key>
|
required |
| Parameter | Description |
|---|---|
order_id
|
The order ID to look up |
Example request
curl https://msdzul.com/api/v1/transactions/INV20260819-001 \
-H "Authorization: Bearer <per-app-api-key>"
Success response (200 OK)
{
"id": "507f1f77bcf86cd799439011",
"app": "my_app",
"webhook_url": "https://your-app.com/webhook/pakasir",
"project": "my-shop",
"order_id": "INV20260819-001",
"amount": 50000,
"fee": 750,
"total_payment": 50750,
"payment_method": "qris",
"txn_id": "vwiqpcriq",
"qr_string": "00020101021226610016ID.CO.SHOPEE.WWW0118...",
"va_number": "",
"status": "completed",
"expired_at": "2026-08-20T12:00:00.000Z",
"completed_at": "2026-08-19T15:30:00.000Z",
"created_at": "2026-08-19T15:00:00.000Z",
"updated_at": "2026-08-19T15:30:00.000Z"
}
Transaction statuses
| Status | Description |
|---|---|
pending
|
Waiting for payment |
completed
|
Payment received |
cancelled
|
Transaction cancelled |
expired
|
Payment window expired |
Error responses
| Status | Error | Description |
|---|---|---|
400
|
order_id is required
|
Missing order_id parameter |
401
|
unauthorized
|
Invalid or missing API key |
404
|
transaction not found
|
Transaction not found in database |
This endpoint receives webhooks from Pakasir. It does not require the app API key. Once a payment is verified, the proxy forwards the payload straight to the webhook_url you registered.
/webhook/pakasir
200
Proxy behavior
webhook_urlResponse
200 OK
The proxy always returns 200 OK to Pakasir to prevent retries. Errors are logged but don't affect the response.
The payload delivered to your application keeps every field of the Pakasir v2 webhook and adds project and payment_method, resolved from the stored transaction.
{
"txn_id": "vwiqpcriq",
"order_id": "INV20260819-001",
"project": "my-shop",
"amount": 50000,
"payment_method": "qris",
"is_sandbox": true,
"status": "completed",
"completed_at": "2026-08-19T15:30:00+07:00"
}
Your application should handle this payload and process the payment accordingly. If you verify project and payment_method only, no change is needed. If you read the transaction reference, use txn_id instead of project + order_id.
Request and response payloads used by the App API.
CreateTransactionRequest
{
"webhook_url": "string",
"order_id": "string",
"amount": "int",
"payment_method": "string"
}
CancelTransactionRequest
{
"order_id": "string",
"amount": "int (deprecated, ignored)"
}
CreateTransactionResponse
{
"txn_id": "string",
"project": "string",
"order_id": "string",
"amount": "int",
"fee": "int",
"total_payment": "int",
"payment_method": "string",
"qr_string": "string",
"va_number": "string",
"expired_at": "string",
"is_sandbox": "boolean",
"status": "string",
"completed_at": "string|null"
}
WebhookPayload
{
"txn_id": "string",
"order_id": "string",
"amount": "int",
"is_sandbox": "boolean",
"status": "string",
"completed_at": "string"
}
ErrorResponse
{
"error": "string"
}
For Pakasir API errors (status 502), the error message contains the raw response from Pakasir. For validation errors (status 400), the message describes the specific field issue.