Webhooks (Callbacks)
When an order's status changes, the system sends an HTTP POST to the notifyUrl specified on the order (or your merchant profile default).
Payload Format
Legacy API callbacks use the compact UTpay format:
{
"refCode": "ORD-260601103045-A1B2",
"orderId": "INV-2026-00123",
"totalAmount": 50000,
"status": "PAID",
"paidAt": "2026-06-01 10:38:12"
}
Fields
| Field | Type | Description |
|---|---|---|
refCode | string | PGA internal reference code |
orderId | string | Your original order ID (the orderId you sent in the create request) |
totalAmount | integer | Full IDR integer — same value you sent in totalAmount |
status | string | PAID, PENDING, or FAILED |
paidAt | string | WIB timestamp ("YYYY-MM-DD HH:MM:SS"); null if not paid |
Status Mapping
| PGA Status | Callback Status |
|---|---|
PAID | PAID |
PENDING | PENDING |
PROCESSING | PENDING |
EXPIRED | FAILED |
FAILED | FAILED |
CANCELLED | FAILED |
REFUNDED | FAILED |
note
PENDING callbacks may fire while an order is still awaiting payment. Always wait for PAID before fulfilling orders.
Timezone
All timestamps in Legacy API callbacks are in WIB (Asia/Jakarta, UTC+7). Format: "YYYY-MM-DD HH:MM:SS".
Responding
Return HTTP 200 to acknowledge. Any other response triggers a retry on the same schedule as V1 webhooks:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | +30s |
| 3 | +2m |
| 4 | +10m |
| 5 | +30m |
| 6 (final) | +2h |
No Signature Header
Legacy callbacks do not include an X-Signature header. If you need to verify the source, consider IP allowlisting or use the query endpoint to confirm order status.
Minimal Handler
PHP — minimal callback handler
<?php
$payload = json_decode(file_get_contents('php://input'), true);
http_response_code(200);
echo json_encode(['received' => true]);
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
if ($payload['status'] === 'PAID') {
// Lookup your order by $payload['orderId']
// Mark as paid in your database
}