Skip to content

GET /v1/payments/{id}

The source of truth for “is this payment succeeded yet?” Use this when you can’t / don’t want to rely on the webhook (polling fallback, post-redirect verification, etc.).

GET /v1/payments/{payment_id}
Host: payment.uncle-z.com
X-PAY-Key: pk_<your app's public key>
X-PAY-Timestamp: <unix seconds>
X-PAY-Signature: <hex hmac-sha256 of "<ts>.GET./v1/payments/<id>.<sha256("")>">

payment_id is the 32-char hex returned by POST /v1/payments.

For GETs the body hash is the SHA-256 of the empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

{
"payment_id": "abc123…",
"status": "succeeded",
"amount_minor": 2900,
"currency": "USD",
"method": "polar",
"provider": "polar",
"provider_ref": "polar_chk_…",
"metadata": { /* echoed verbatim from create + PSP enrichment */ },
"invoice_number": "FTY-PRO-000042",
"last_error": "",
"created_at": "2026-05-09T12:34:56Z",
"updated_at": "2026-05-09T12:35:01Z"
}
statusMeaning
initiatedPayment created, buyer hasn’t completed checkout yet (or PSP hasn’t notified us).
succeededPSP confirmed the buyer paid. Terminal.
failedPSP rejected the payment, or buyer abandoned and the gateway recorded a terminal failure.
canceledPayment was canceled by buyer / admin / PSP before completion. Terminal.

succeeded, failed, canceled are terminal — once a payment hits one, it doesn’t move again.

When set, contains the verbatim PSP-side error string from the most recent failure. Useful for support tickets — “why did this payment fail?” returns a NICEPAY error code or Polar error message directly.

async function waitForTerminal(paymentId: string, intervalMs = 1000, timeoutMs = 1200000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const r = await getPayment(paymentId);
if (['succeeded', 'failed', 'canceled'].includes(r.status)) return r;
await new Promise(r => setTimeout(r, intervalMs));
}
throw new Error('payment never reached terminal state');
}

20 minutes is a reasonable upper bound for QRIS (which has a hard 20-min expiry); shorter for cards (Polar/PayPal usually settle in seconds).

StatusBodyMeaning
401missing auth headers / bad signature / etc.Same auth errors as POST.
404payment not foundThe id doesn’t exist OR doesn’t belong to your app. (Tenant scoping is enforced — you can’t read another product’s payments by guessing ids.)