API documentation
PProMM verifies mobile wallet payment receipts (KPay, KBZPay, Wave, AYA Pay, MMQR and more) and returns one of three verdicts: approved, pending review or rejected — with a machine readable reason.
Overview
https://ppromm.com
Machine readable spec (OpenAPI 3.1) — import into Postman, Insomnia, Swagger UI or a client generator:
https://ppromm.com/api/public/openapi.jsonDownload OpenAPI JSON
- Register your app in the PProMM console (Clients) and copy the API key and webhook secret.
- When your customer uploads a receipt, POST the image plus what you expected to be paid.
- Act on the verdict. If it is
pending_review, poll the status endpoint or wait for the signed webhook.
Authentication
Send your API key in the x-api-key header on every request. Keys start with rv_ and must never be exposed in browser code — call PProMM from your backend.
x-api-key: rv_live_xxxxxxxxxxxxxxxxxxxx
A missing, invalid, or deactivated key returns 401 Invalid or inactive API key.
POST /api/public/verify-receipt
Submits a receipt image for verification. Responds synchronously with the verdict.
| Field | Type | Notes |
|---|---|---|
| image_base64 | string | Raw base64 or a data URL. Required unless image_url is given. Max 10MB decoded. |
| image_url | string | Publicly reachable image URL. Alternative to image_base64. |
| expected_amount | number | Amount your app expects, e.g. 25000. |
| currency | string | Defaults to MMK. |
| expected_receiver | string | Merchant name or account the money must arrive at. |
| wallet | string | Optional hint: kpay, kbzpay, wavepay, ayapay, mmqr… |
| reference | string | Your own order/invoice id, echoed back as reference. |
| callback_url | string | Overrides the app's default webhook URL for this submission. |
curl -X POST https://ppromm.com/api/public/verify-receipt \
-H "x-api-key: rv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"image_base64": "data:image/jpeg;base64,/9j/4AAQ...",
"expected_amount": 25000,
"currency": "MMK",
"expected_receiver": "Aung Aung",
"wallet": "kpay",
"reference": "order_9182",
"callback_url": "https://your-app.com/api/public/payment-webhook"
}'200 OK
{
"submission_id": "0f3c9d1e-2b7a-4c55-9f21-8a1d4e6b7c90",
"verdict": "approved",
"confidence": 0.94,
"reason": "Amount, receiver and transaction ID all matched.",
"reason_codes": [],
"extracted": {
"wallet": "KBZPay",
"amount": 25000,
"currency": "MMK",
"sender": "Mg Mg",
"receiver": "Aung Aung",
"receiver_account": "09xxxxxxxxx",
"txn_id": "1234567890",
"paid_at": "2026-08-16T12:04:00Z",
"notes": null
},
"checks": [
{ "id": "amount", "label": "Amount matches", "passed": true },
{ "id": "receiver", "label": "Receiver matches", "passed": true },
{ "id": "duplicate", "label": "Transaction not reused", "passed": true }
],
"ai_verdict": "genuine"
}GET /api/public/verify-receipt/{id}
Polls a submission. Only submissions belonging to your API key are visible. Use it while a verdict is pending_review.
curl https://ppromm.com/api/public/verify-receipt/0f3c9d1e-2b7a-4c55-9f21-8a1d4e6b7c90 \
-H "x-api-key: rv_live_xxx"
200 OK
{
"submission_id": "0f3c9d1e-…",
"reference": "order_9182",
"verdict": "approved",
"confidence": 0.72,
"reason": "Approved by reviewer",
"reason_codes": ["LOW_CONFIDENCE"],
"extracted": { "…": "…" },
"checks": [ "…" ],
"reviewed_at": "2026-08-16T12:20:11Z",
"created_at": "2026-08-16T12:05:02Z"
}Recommended polling: every 10 seconds for up to 15 minutes, then fall back to the webhook.
Verdicts
Reason codes
| AMOUNT_MISMATCH | Amount does not match the expected payment |
| RECEIVER_MISMATCH | Receiver account does not match the merchant account |
| DUPLICATE_TXN | This transaction ID was already submitted |
| IMAGE_UNREADABLE | Image is blurry, cropped or unreadable |
| TAMPER_SUSPECTED | Signs of editing or tampering detected |
| NOT_A_RECEIPT | Image is not a payment receipt |
| MISSING_TXN_ID | No transaction ID could be read |
| MISSING_AMOUNT | No amount could be read |
| TOO_OLD | Payment time is outside the accepted window |
| LOW_CONFIDENCE | AI confidence is too low for an automatic decision |
| AI_ERROR | Verification service could not analyse the image |
Webhooks
When a reviewer finalises a pending_review submission, PProMM POSTs to your callback URL.
POST https://your-app.com/api/public/payment-webhook
X-Receipt-Timestamp: 1755350411000
X-Receipt-Signature: <hex hmac-sha256>
Content-Type: application/json
{
"event": "verification.reviewed",
"submission_id": "0f3c9d1e-…",
"reference": "order_9182",
"verdict": "approved",
"reviewed_at": "2026-08-16T12:20:11Z",
"note": "Bank app screenshot confirmed",
"extracted": { "…": "…" }
}Verify the signature before trusting the payload: it is an HMAC-SHA256 of `${timestamp}.${rawBody}` using your webhook secret (whsec_…).
import { createHmac, timingSafeEqual } from "crypto";
const raw = await request.text();
const ts = request.headers.get("x-receipt-timestamp")!;
const sig = request.headers.get("x-receipt-signature")!;
const expected = createHmac("sha256", process.env.PAYMENTPRO_SECRET_KEY!)
.update(`${ts}.${raw}`)
.digest("hex");
if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("Invalid signature", { status: 401 });
}
const payload = JSON.parse(raw);Respond with 2xx. Non-2xx responses are recorded as a failed delivery on the submission.
Errors
| 400 | Invalid JSON, failed validation, unreadable image, or image_url could not be fetched |
| 401 | Invalid or inactive API key |
| 404 | Submission not found for this API key |
| 413 | Image is larger than 10MB |
| 500 | Verification failed — safe to retry once |
Duplicate protection is global: once a transaction ID is accepted it can never be reused, so your app does not need its own duplicate check.