Get payment link status API
Reads back a payment link by your reference, with the status of the link and the outcome of the payment made through it. Use it to recover the result of a payment when a notification did not reach you, and to check the state of a link before you issue a new one.
Endpoint
GET https://app.papi.mg/engine/api/payment-links/{merchantPaymentReference}
| Item | Value |
|---|---|
| Base URL | https://app.papi.mg/engine/api |
| Path | /payment-links/{merchantPaymentReference} |
| Method | GET |
| Request body | None |
| Response body | JSON |
This endpoint exists only on the engine URL. The legacy URL https://app.papi.mg/dashboard/api/payment-links supports link creation (POST) only. It does not support this GET.
Authentication
Every request must carry your shop's API key in the Token header. The reference is resolved inside the shop that the API key belongs to only.
Request headers
| Header | Required | Value | Description |
|---|---|---|---|
Token | ✓ | <YOUR_API_KEY> | API key of your shop. |
No other header is read by this endpoint. The LinkToken header used by the payment page is not accepted as an authentication method here.
Path parameters
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
merchantPaymentReference | string | ✓ | Matched exactly (case-sensitive). May contain dots. URL-encode reserved characters. | Your reference: the reference field you sent when you created the link. It is the same value as merchantPaymentReference in notifications. It is not Papi's payment reference. |
Use only letters, digits, -, _ and . in references. Characters such as ?, # or spaces must be URL-encoded (%3F, %23, %20). A reference containing / cannot be read back through this endpoint, even URL-encoded: the server refuses an encoded slash in a path.
Which link is returned
A reference is not always unique: you can create a new link with the same reference after the previous one was paid, expired, or disabled. When several links in your shop share the reference, the endpoint returns exactly one link, chosen in this order:
- A paid link, if one exists, whatever its age. If several are paid, the most recent paid link.
- Otherwise, the most recent link.
So if an order was paid and a new link was issued for it later, this endpoint still reports the paid link. You are always told that the money came in.
This lookup considers every link of your shop with this reference, including links created by hand from the dashboard.
Request example
curl -X GET "https://app.papi.mg/engine/api/payment-links/ORDER-123" \
-H "Token: <YOUR_API_KEY>"
Success response
Status: 200 OK
{
"data": {
"linkStatus": "PAID",
"paymentStatus": "SUCCESS",
"paymentMethod": "MVOLA",
"currency": "MGA",
"displayCurrency": "MGA",
"amount": 15000.0,
"clientName": "Client Name",
"description": "Payment for Order #123",
"merchantPaymentReference": "ORDER-123",
"papiPaymentReference": "c1f4a5b0-6f5e-4e1b-9f0e-2b7d8a9c3d21",
"notificationToken": "5b8f0c3e-2a7d-4f61-9e0b-7c4d1a2e9f38",
"message": null,
"payerEmail": "customer@example.com",
"payerPhone": "+261 34 00 000 00",
"paymentLink": "https://payment-form.papi.mg/yourshop/payments/eyJhbGciOiJIUzI1NiJ9...",
"shortLink": "https://link.papi.mg/8NW7R",
"linkCreationDateTime": 1788065989011,
"linkExpirationDateTime": 1788281989011,
"isTestMode": false
}
}
Response fields
Every field is present in the response. A field without a value is null.
The fields have the same names and meanings as in the notification body, with one exception: Papi's reference is called papiPaymentReference here (it is paymentReference in the notification). This avoids any confusion with your merchantPaymentReference.
| Field | Type | Description |
|---|---|---|
linkStatus | string | State of the link: ACTIVE, EXPIRED, PAID or DISABLED. See Link status. |
paymentStatus | string | Outcome of the payment attempt described by this response: SUCCESS, PENDING or FAILED (the same values as the notification). null when nobody has tried to pay. |
paymentMethod | string | Provider of that payment attempt (MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED). null when nobody has tried to pay. |
currency | string | Currency of the link (MGA). |
displayCurrency | string | Currency shown to the customer (MGA). |
amount | number | Amount of the link. |
clientName | string | Customer's name, as sent at creation. |
description | string | Description, as sent at creation. |
merchantPaymentReference | string | Your reference, as sent at creation. |
papiPaymentReference | string | Papi's reference for the payment attempt (a UUID). It is the paymentReference of the notification. null when nobody has tried to pay. |
notificationToken | string | The token returned when the link was created. |
message | string | Failure reason of the payment attempt, when there is one. null otherwise. |
payerEmail | string | Customer's email, as sent at creation, or null. |
payerPhone | string | Customer's phone number in international format, as sent at creation, or null. |
paymentLink | string | The payment URL, as returned at creation. |
shortLink | string | Short form of the payment URL, or null when none was generated. |
linkCreationDateTime | integer | Creation time, epoch milliseconds. |
linkExpirationDateTime | integer | Expiry time, epoch milliseconds. |
isTestMode | boolean | Whether the link is flagged as a test. |
Link status
linkStatus is evaluated in this order. The first rule that matches gives the status:
| Order | Condition | linkStatus |
|---|---|---|
| 1 | The link is paid. | PAID |
| 2 | The expiry time is in the past. | EXPIRED |
| 3 | The link is enabled. | ACTIVE |
| 4 | None of the above: the link was switched off. | DISABLED |
PAID always wins: a paid link is reported as PAID even after it expired or was disabled. A link that was switched off and whose expiry time is in the past is reported as EXPIRED.
Which payment attempt is described
A customer can try to pay several times on the same link (for example, a first attempt fails and a second one succeeds). The payment fields (paymentStatus, paymentMethod, papiPaymentReference, message) describe one attempt:
- for a
PAIDlink: the successful attempt, even if another attempt was made after it; - for any other link: the most recent attempt.
How to read the two statuses together
linkStatus | paymentStatus | Meaning | What to do |
|---|---|---|---|
ACTIVE | null | Nobody has tried to pay yet. | Wait, or send the link to the customer again. |
ACTIVE | FAILED | The last attempt was declined or abandoned. The customer can try again on the same link. | Read message for the reason. Wait. |
ACTIVE | PENDING | An attempt is in progress at the provider. | Check again shortly. |
PAID | SUCCESS | The payment went through. | Confirm the order. |
EXPIRED | null / FAILED / PENDING | The link expired before a successful payment. | Create a new link. |
DISABLED | null / FAILED / PENDING | The link was switched off from the dashboard. | Create a new link if the order must still be paid. |
Errors
Error body format:
{
"error": {
"code": "<ERROR_CODE>",
"message": "<Human-readable message, in French>"
}
}
| HTTP status | Error code | Meaning | Example message |
|---|---|---|---|
401 | CORE_PERM_0001 | The Token header is missing, or the API key is unknown. | API key invalide |
404 | CORE_404 | No link with this reference exists in your shop. A reference that exists only in another shop also gives 404. | Aucun lien de paiement pour la référence ORDER-123 |
Example 404 body:
{
"error": {
"code": "CORE_404",
"message": "Aucun lien de paiement pour la référence ORDER-123"
}
}
Practical notes
Recovering a missed notification
A notification is sent once. If your endpoint was down or the call was lost:
- Call this endpoint with your
reference. - If
linkStatusisPAIDandpaymentStatusisSUCCESS, confirm the order. - If
paymentStatusisPENDING, call the endpoint again later. - Otherwise, keep the order unpaid.
You can also resend the notification from the dashboard: see Failed notifications.
Checking before you issue a link again
Before you create a new link for an order that already had one, call this endpoint:
PAID: do not issue a new link. The order is paid.ACTIVE: send the same link to the customer again, or send the same creation request again (it returns the same link, see Idempotency).EXPIREDorDISABLED: create a new link. You can use the samereference.
Polling
This endpoint is intended for recovery and checks, not as a replacement for notifications. If you poll, space the calls out (for example, every few seconds while paymentStatus is PENDING) and stop when the link is PAID, EXPIRED or DISABLED.
Test mode
The endpoint reads test links and live links in the same way. Use isTestMode in the response to tell them apart.