API reference
Create payment records and collect live PKR payments with JazzCash, EasyPaisa, or OneQR. Customers can open hosted checkout at /pay/{payment_id}. Test payments do not contact a provider or credit a balance. Payment webhooks are sent only after a stored payment changes state.
Confirmation required
Authentication
Send the public key and secret from the merchant dashboard. The merchant is taken from the key. Do not send a merchant id.
X-GetPay-Key: gp_pk_test_... Authorization: Bearer gp_sk_test_...
Environments
Test keys create test payments and never contact the provider or credit a balance. Live keys can collect PKR. They do not share idempotency keys or payment lists. A test payment cannot be switched to live. Live collection credits pending funds only, never available balance.
Create a payment
POST /api/v1/payments
Amount is a string. PKR allows 2 decimal places and USDT allows 6. Send Idempotency-Key on every create. Omit payment_method to record a payment without contacting a provider. jazzcash and easypaisa also require payer_email and an 11-digit mobile_number starting with 03. oneqr requires payer_email. The return address is the approved website and cannot be overridden. Do not send return or return_url.
payment_method usdt with currency USDT opens a BNB Smart Chain invoice. Omit network or send bsc. Other networks and assets are rejected. The response includes the receiving address and a status URL. It does not include an RPC endpoint. The create response is not a payment. Pending USDT is credited only after the configured confirmations. A test invoice is not credited and does not contact a blockchain provider.
{
"amount": "1500.00",
"currency": "PKR",
"merchant_reference": "ORDER-1001",
"description": "Order 1001",
"expires_in_minutes": 60
}
The same merchant, environment, idempotency key, and request returns the original payment. The same key with a different request returns 409. Another merchant can reuse the key.
Read payments
GET /api/v1/payments/{payment_id}
GET /api/v1/payments
Results are limited to the authenticated merchant and the key's environment. Filters: status, merchant_reference, currency, created_from, created_to. Page size is at most 50. Payment ids look like PY-YYYYMMDD-XXXXXX and are not your merchant reference.
Withdrawals
POST /api/v1/withdrawals
GET /api/v1/withdrawals/{withdrawal_id}
GET /api/v1/withdrawals
PKR methods are jazzcash, easypaisa, and bank. USDT BEP20 is usdt_bep20 on BNB Smart Chain and pays only the approved address. Send Idempotency-Key. A live withdrawal reserves the requested amount before any provider call. The response amount is that request, fee is the stored GetPay fee, and net_amount is what the beneficiary or chain receives. A test withdrawal does not move funds or touch the chain. The response uses the GetPay withdrawal id. PKR recipients are masked. A USDT response can include the destination, network, and transaction hash. It does not include provider cost, gas, credentials, private keys, raw transactions, or ledger ids. With no fee schedule the fee is 0. Pending collection funds are not withdrawable.
Statuses
- PENDING — recorded, waiting.
- PROCESSING — confirmation in progress.
- PAID — verified live collection credited pending funds. Not set by this API.
- COMPLETED — paid payment finished internally.
- FAILED — did not complete.
- EXPIRED — not paid in time. It cannot later become paid.
- REFUNDED — a completed payment was refunded.
- REVERSED — a paid or completed payment was reversed.
- DISPUTED — a paid or completed payment is disputed.
Webhooks
GetPay posts JSON to the HTTPS endpoint saved in the merchant dashboard. A webhook is created from a committed payment state change. Opening checkout, calling the API, or receiving a provider notification does not call your server by itself. Delivery is queued after the payment transaction commits, so your endpoint cannot delay or roll back the payment.
Events
Current payload version 2026-10-05. Amount is the customer gross. fee and net_amount are the stored quote. A retry sends the original body and does not recalculate the fee.
- payment.created — a payment record was stored.
- payment.status_changed — the payment moved to a new status.
- payment.paid, payment.completed, payment.failed, payment.expired, payment.reversed, payment.refunded — sent in addition to payment.status_changed when that status is reached.
Events for one payment are not guaranteed to arrive in order. Each event has its own id, the payment status at that moment, and a timestamp. Read GET /api/v1/payments/{payment_id} for the current status. Dedupe with the event id. A retry of the same event reuses that id, the same JSON body, and the same timestamp.
Endpoint
One HTTPS URL per merchant. IP addresses, private hosts, localhost, link-local and metadata names, credentials, and query strings are rejected. The address is checked again immediately before each delivery, including DNS. Redirects are not followed. Turn the endpoint off to stop delivery. A disabled endpoint or a merchant who is not active does not receive new deliveries.
Signature
Create a signing secret in the dashboard. It is shown once and is separate from the API secret and from provider credentials. The secret is stored encrypted. After rotation, the previous secret remains valid for 24 hours. During that overlap, X-GetPay-Signature-Previous is the signature made with the previous secret. Queued deliveries are signed at send time and keep the original event id and body.
X-GetPay-Event-Id: evt_... X-GetPay-Timestamp: 1759582800 X-GetPay-Signature: hex hmac X-GetPay-Webhook-Version: 2026-10-05 X-GetPay-Attempt: 1 X-GetPay-Delivery-Id: dlv_...
The signature is hex HMAC-SHA256 of timestamp + "." + raw_body, using the signing secret. timestamp is the event time in Unix seconds, not the time of the retry. Compare the signature to the exact request body with a constant-time check. Do not parse and re-encode the JSON before verifying. Reject a timestamp more than 5 minutes in the future or more than 24 hours old.
X-GetPay-Delivery-Id changes on every attempt. Do not use it as an idempotency key. Use X-GetPay-Event-Id.
Payload
{
"id": "evt_...",
"type": "payment.paid",
"api_version": "2026-10-05",
"created": "2026-10-04T17:00:00+05:00",
"data": {
"payment": {
"id": "PY-20261004-AB23CD",
"merchant_reference": "ORDER-1001",
"amount": "1000.00",
"fee": "0.00",
"net_amount": "1000.00",
"currency": "PKR",
"status": "PAID",
"environment": "live"
}
}
}
Amounts are strings. id inside payment is the public GetPay reference, not your merchant reference. The body does not include database ids, ledger accounts, provider payloads, provider signatures, API secrets, or the webhook signing secret.
Retries
Return any HTTP status from 200 to 299 to accept the event. Anything else is not treated as delivered. Redirects are a failure and are not followed. GetPay retries timeouts, connection failures, and HTTP 408, 429, 500, 502, 503, and 504. It does not keep retrying HTTP 400, 401, 403, or 404. A valid Retry-After on HTTP 429 is respected up to one hour. The schedule after the first attempt is 30 seconds, 2 minutes, 10 minutes, 30 minutes, and 2 hours, with at most 6 attempts. There is no manual retry in the dashboard. One merchant's endpoint cannot block another merchant or the payment.
What to do when you receive one
- Verify the signature and timestamp before trusting the body.
- Store the event id and ignore repeats.
- Return 2xx only after you have stored the event.
- If you need the latest payment, call the payments API. Do not rely on arrival order.
Errors
{
"success": false,
"error": {
"code": "validation_failed",
"message": "The payment request is not valid.",
"fields": { "amount": ["Amount must be greater than zero."] }
}
}
- 401 — invalid or revoked credentials.
- 403 — the merchant cannot accept payments.
- 404 — the payment is not visible to this key.
- 409 — idempotency conflict or invalid status change.
- 422 — validation failed.
- 429 — rate limit exceeded.
- 500 — generic internal error.