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:
{
"clientId": "TEST_CLIENT_001",
"refCode": "ORD-260601103045-A1B2",
"transactionTime": "2026-06-01 10:30:45",
"orderId": "INV-2026-00123",
"totalAmount": 50000,
"customerName": "Budi Santoso",
"customerEmail": "budi@example.com",
"customerPhone": "6281234567890",
"status": "PAID",
"paidAt": "2026-06-01 10:38:12",
"signature": "lowercase-sha512-hex"
}
Fields
| Field | Type | Description |
|---|---|---|
refCode | string | FlyPay internal reference code |
clientId | string | Your unchanged UTpay client ID |
transactionTime | string | Order creation time in WIB |
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 |
signature | string | SHA-512 verification signature |
Status Mapping
| FlyPay 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 |
Callback Signature
The signature is in the JSON body, as in UTpay:
signature = lowercase_hex(SHA512(clientId + "|" + orderId + "|" + refCode + "|" + status + "|" + clientSecret))
Compare it in constant time and use the query endpoint when reconciliation is required.
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
}