Introduction
Welcome to the ProxyPay API to interface with EMIS GPO. This allows merchant applications or services to request a payment from a client, via Multicaixa Express (GPO), using the mobile phone number.
API Format
All request bodies should have content type application/json and be valid JSON. The Content-Type header must be set in all requests that include a JSON body:
Content-Type: application/json
Endpoints
| Environment | Base URL |
|---|---|
| Production | https://api.proxypay.co.ao/ |
| Sandbox | https://api.sandbox.proxypay.co.ao/ |
Authentication
You authenticate by providing a bearer token in the Authorization header of every request:
Authorization: Bearer [BEARER_TOKEN]
Transaction Lifecycle
The transaction is initiated by the merchant side and must be accepted by the customer on the Multicaixa Express App.
- Merchant submits the transaction request. (See Transactions).
- The API creates the transaction and initiates a request to GPO.
- Once the GPO request returns, the callback URL receives an asynchronous HTTP(S) POST with the updated transaction object as payload. (See Transaction Schema).
Errors
The API may return the following HTTP error codes:
| Error Code | Meaning |
|---|---|
| 400 | Bad Request -- Your request is invalid. |
| 401 | Unauthorized -- Your API key is wrong. |
| 403 | Forbidden -- The resource requested is not accessible to the caller. |
| 404 | Not Found -- The specified resource could not be found. |
| 405 | Method Not Allowed -- You tried to access a resource with an invalid method. |
| 406 | Not Acceptable -- You requested a format that isn't json. |
| 429 | Too Many Requests -- You're requesting too much! Slow down! |
| 500 | Internal Server Error -- We had a problem with our server. Try again later. |
| 503 | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. |
Resources
Transactions
New Payment
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4b3dc944-e30d-49a5-9a94-923f72d11837" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod" \
"https://<base_url>/opg/v1/transactions" \
-d '{
"type": "payment",
"pos_id": 123,
"mobile": "900123456",
"amount": "123.45",
"callback_url": "https://your_hostname/link/to/confirm"
}'
If the input is valid the transaction is created.
HTTP/1.1 201 Created
{
"amount": "123.45",
"id": "2KaABHhzBVa6qFHeJOebJDle8vG",
"mobile": "900123456",
"pos_id": 123,
"type": "payment"
}
This endpoint creates a new payment transaction.
HTTP Request
POST https://<base_url>/opg/v1/transactions
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | true | Must be set to "payment" |
| pos_id | integer | true | The POS_ID assigned by EMIS |
| mobile | string | true | The client mobile number in a string format. Must be a number registered in Multicaixa Express. |
| amount | string | true | Amount of the requested payment in a string format. Use (.) as decimals separator; at most 2 decimal places |
| callback_url | string | false | Optional URL that will receive a POST request with the transaction payload once the charge is completed (accepted or rejected) |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Payment request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
New Refund
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 57feeb24-0b1c-4f53-82bf-9fd74d8249e1" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod" \
"https://<base_url>/opg/v1/transactions" \
-d '{
"type": "refund",
"parent_transaction_id": "2KaABHhzBVa6qFHeJOebJDle8vG",
"callback_url": "https://your_hostname/link/to/confirm"
}'
If the input is valid the transaction is created.
HTTP/1.1 201 Created
{
"id": "2KayAfTmbQCIUPoWlsV4G6jsc1T",
"parent_transaction_id": "2KaABHhzBVa6qFHeJOebJDle8vG",
"type": "refund"
}
This endpoint creates a new refund transaction. Only full refunds are currently supported.
HTTP Request
POST https://<base_url>/opg/v1/transactions
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | true | Must be set to "refund" |
| parent_transaction_id | string | true | The ID of a transaction of type "payment" to be refunded. |
| callback_url | string | false | Optional parameter to set the callback URL that will receive the transaction once it completes |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Refund request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
New Authorization
curl -XPOST \
"Content-Type: application/json" \
-H "Idempotency-Key: 1a63deb8-9c8b-40a0-ac77-2dde5778d4b5" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod" \
"https://<base_url>/opg/v1/transactions" \
-d '{
"type": "authorization",
"pos_id": 123,
"mobile": "900111222",
"amount": "123.45",
"callback_url": "https://your_hostname/link/to/confirm"
}'
If the input is valid the transaction is created.
HTTP/1.1 201 Created
{
"amount": "123.45",
"id": "2Kb2wXfV0YOkQo0I51fBcoTLsbq",
"mobile": "900111222",
"pos_id": 123,
"type": "authorization"
}
This endpoint creates a new payment authorization transaction. If accepted, funds will be held until an explicit payment with authorization transaction is submitted and accepted or until the authorization is either canceled or expires.
HTTP Request
POST https://<base_url>/opg/v1/transactions
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | true | Must be set to "authorization" |
| pos_id | integer | true | The POS_ID assigned by EMIS |
| mobile | string | true | The client mobile number in a string format. Must be a number registered in Multicaixa Express. |
| amount | string | true | Amount to pre-authorize in a string format. Use (.) as decimals separator; at most 2 decimal places |
| callback_url | string | false | Optional parameter to set the callback URL that will receive the transaction details once it completes |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Payment authorization request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
New Cancelation
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 042558be-67db-47ad-aeb8-6aa5ad927361" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod" \
"https://<base_url>/opg/v1/transactions" \
-d '{
"type": "cancelation",
"parent_transaction_id": "2Kb2wXfV0YOkQo0I51fBcoTLsbq",
"callback_url": "https://your_hostname/link/to/confirm"
}'
If the input is valid the transaction is created.
HTTP/1.1 201 Created
{
"id": "2Kb3fXeArMGefP0if0CJ71DA5eP",
"parent_transaction_id": "2Kb2wXfV0YOkQo0I51fBcoTLsbq",
"type": "cancelation"
}
This endpoint creates a new cancelation transaction for a previously accepted authorization.
HTTP Request
POST https://<base_url>/opg/v1/transactions
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | true | Must be set to "cancelation". |
| parent_transaction_id | string | true | ID of a previously accepted authorization to cancel |
| callback_url | string | false | This is an optional value that expects the callback url that we will call as soon the request has been completed. |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Cancelation request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
New Payment w/ Authorization
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: e0baf9b9-356a-4b13-9efe-5a23b8a61b9e" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod" \
"https://<base_url>/api/v1/transactions" \
-d '{
"type": "payment",
"parent_transaction_id": "2Kb2wXfV0YOkQo0I51fBcoTLsbq",
"amount": "123.45",
"callback_url": "https://your_hostname/link/to/confirm"}'
If the input is valid the transaction is created.
HTTP/1.1 202 Accepted
location: /api/v1/requests/1kTFGhJH8i58uD9MdJpMjWnoE
This endpoint creates a new payment transaction from a previously accepted authorization, efectively capturing the amount.
HTTP Request
POST https://<base_url>/opg/v1/transactions
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | true | Must be set to "payment" |
| parent_transaction_id | string | true | The ID of the accepted authorization transaction |
| amount | string | true | Amount to capture. Must be equal or less than the amount of the authorization. Use (.) as decimals separator; at most 2 decimal places |
| callback_url | string | false | Optional parameter to set the callback URL that will receive the transaction details once it completes |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Payment request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
Get Transaction
curl "https://<base_url>/opg/v1/transactions/2KaABHhzBVa6qFHeJOebJDle8vG" \
-H "Authorization: Bearer ve70pfacln98c7pc8j802pisdsg0f9i2anj5jlc3vtsfitod"
The above command returns a structure like this:
HTTP/1.1 200 OK
{
"amount": "123.45",
"id": "2KaABHhzBVa6qFHeJOebJDle8vG",
"mobile": "900123456",
"parent_transaction_id": null,
"pos_id": 123,
"status": "accepted",
"status_datetime": "2023-01-10T08:09:55Z",
"status_reason": null,
"type": "payment"
}
This endpoint retrieves a specific transaction.
HTTP Request
GET https://<base_url>/opg/v1/transactions/<ID>
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the transaction to retrieve |
Responses
| Status | Meaning | Description |
|---|---|---|
| 200 | OK | Transaction has been retrieved. |
| 4xx or 5xx | Error | Please check the error table. |
Transaction Schema
The following table details the transaction schema:
Schema
| Attribute | Type | Nullable | Max. Length | Comments |
|---|---|---|---|---|
| Id | string | false | 30 | |
| type | string | false | 10 | Values: "payment" or "refund" or "purchase"; Charge transactions are of type "purchase" |
| pos_id | integer | true | Can be null in a refund with invalid parent_transaction_id | |
| parent_transaction_id | string | true | 30 | Set if refund, null if payment |
| mobile | string | true | 12 | Can be null in a refund with invalid parent_transaction_id; For purchase (charge) transactions, only a masked number is returned |
| amount | string | true | 20 | Can be null in a refund with invalid parent_transaction_id; Dot(.) as decimals separator;at most 2 decimal places |
| status | string | true | Set to "accepted" or "rejected" (See Status) when execution completes | |
| status_datetime | string | true | Set to ISO8601 datetime when execution completes | |
| status_reason | string | true | 20 | Set if status is "rejected" (See Status Reason) |
Status
- accepted: payment/refund accepted by the payment system or customer
- rejected: payment/refund rejected by the payment system or customer
Status Reason
Gateway
| Code | Meaning |
|---|---|
| 1000 | Generic gateway error |
| 1001 | Request timed-out and will not be processed |
| 1002 | Gateway is not authorized to execute transactions on the specified POS |
| 1003 | Parent transaction is not valid for the request |
Processor
| Code | Meaning |
|---|---|
| 2000 | Generic processor error |
| 2001 | Insufficient funds in client's account |
| 2002 | Refused by the card issuer |
| 2003 | Card or network daily limit exceeded |
| 2004 | Request timed-out and was refused by the processor |
| 2005 | POS is closed and unable to accept transactions |
| 2006 | Insufficient funds in POS available for refund |
| 2007 | Invalid or Inactive supervisor card |
| 2008 | Invalid merchant email |
| 2009 | Parent transaction is too old to be refunded |
| 2010 | Request was refused by the processor |
| 2011 | Payment amount exceeds Authorization amount |
| 2012 | Parent transaction already processed |
Client
| Code | Meaning |
|---|---|
| 3000 | Refused by client |
Idempotency
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4b3dc944-e30d-49a5-9a94-923f72d11837" \
...
Notice the Idempotency-Key is passed as a Header to the POST HTTP request
To ensure the client application can implement a simple retry mechanism to handle timeouts, due to temporary communication failures with the API, an optional custom HTTP header Idempotency-Key can be included in the request of a new payment or refund transaction.
Idempotency-Key: [IDEMPOTENCY_KEY]
The Idempotency-Key should include a random and unique string. When retrying, the same value MUST be passed.
The server will cache the value along with the input parameters in the request payload. The following logic will be applied:
If no Idempotency-Key header is passed, the server will not try to enforce any idempotency guarantee.
If an Idempotency-Key header is passed, the server will do a lookup in the cache, using the header value as lookup key.
- If the key is not found, the request will be processed as a new call, resulting in a request sent to the payment provider
- If the key is found, and the request payload matches what is stored in the cache, the previous HTTP response will be returned, without processing the transaction again.
- If the key is found, and the request payload does NOT match what is stored in the cache, an HTTP StatusCode 400 is returned.
The cache entries will be stored for 5 minutes, ensuring the server will keep a de-duplication window of 5 minutes.
Charges
The charges resource is currently only available in the sandbox environment. This new resource allows to initiate a payment either via QR-Code or via DeepLink. The normal flow is:
- The merchant creates a charge and provides the client with the QR-Code, the deeplink, or the deeplink redirect URL.
- The client scans the QR-Code or triggers the Deeplink (directly or via the redirect URL) and completes the transaction.
After the charge is created the status will be Active. Once the charge is used (via QR-Code or Deeplink), regardless of success or rejection, it can no longer be used. If not used the charge will expire after 30 minutes have passed since creation.
Charge Lifecycle
A charge transitions through the following statuses:
| Status | Description | Final |
|---|---|---|
| active | Charge created and awaiting use | No |
| used | Charge scanned/triggered via QR-Code or Deeplink | Yes |
| expired | Charge was not used within 30 minutes of creation | Yes |
State Diagram
┌──────────┐
│ active │
└────┬─────┘
│
├── Client scans QR-Code / triggers Deeplink ───► ┌──────┐
│ │ used │
│ └──────┘
│
└── 30 minutes elapse without use ──────────────► ┌─────────┐
│ expired │
└─────────┘
Lifecycle Details
Active — A charge enters this status immediately upon creation. The merchant presents the
qrcode_url,deeplink, ordeeplink_redirectto the client.Used — When the client scans the QR-Code or triggers the Deeplink, the charge transitions to
usedand a transaction is created. Thetransaction_idfield is populated with the resulting transaction ID. This occurs regardless of whether the transaction isacceptedorrejected— a used charge cannot be reused.Expired — If 30 minutes pass since
created_atwithout the charge being used, it automatically transitions toexpired. No transaction is created.
Callback Notification
If a callback_url was provided when creating the charge, a POST request with the transaction payload is sent to that URL once the charge transitions to used. This is the recommended way to be notified of charge completion rather than polling the Get Charge endpoint.
New Charge
curl -XPOST \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4b3dc944-e30d-49a5-9a94-923f72d11837" \
-H "Authorization: Bearer <token>" \
"https://<base_url>/opg/v1/charges" \
-d '{
"pos_id": 123,
"amount": "123.45",
"callback_url": "https://your_hostname/link/to/confirm"
}'
If the input is valid the charge is created.
HTTP/1.1 201 Created
{
"amount": "123.45",
"created_at": "2025-11-02T16:02:44Z",
"deeplink": "mcxwallet://purchase?qrref=...",
"deeplink_redirect": "https://deeplink.proxypay.co.ao/...",
"expires_at": "2025-11-02T16:32:44Z",
"id": "34vgGPAKbVTMmmsiyy3S7LYihDB",
"pos_id": 123,
"qrcode_url": "https://qrcode.proxypay.co.ao/...",
"status": "active",
"transaction_id": null
}
This endpoint creates a new charge that can be used to generate a QRCode or a Deeplink.
HTTP Request
POST https://<base_url>/opg/v1/charges
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| amount | string | true | Amount of the requested charge in a string format. Use (.) as decimals separator; at most 2 decimal places |
| pos_id | integer | true | The POS_ID assigned by EMIS |
| callback_url | string | false | Optional URL that will receive a POST request with the transaction payload once the charge is completed (accepted or rejected) |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Charge request has been created for processing. |
| 4xx or 5xx | Error | Please check the error table. |
Get Charge
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
"https://<base_url>/opg/v1/charges/34vgGPAKbVTMmmsiyy3S7LYihDB"
If the id of the charge exists it will return the charge details.
HTTP/1.1 200 OK
{
"amount": "123.45",
"created_at": "2025-11-02T16:02:44Z",
"deeplink": "mcxwallet://purchase?qrref=...",
"deeplink_redirect": "https://deeplink.proxypay.co.ao/...",
"expires_at": "2025-11-02T16:32:44Z",
"id": "34vgGPAKbVTMmmsiyy3S7LYihDB",
"pos_id": 444,
"qrcode_url": "https://qrcode.proxypay.co.ao/...",
"status": "active",
"transaction_id": null
}
This endpoint returns the details of the charge identified by charge_id.
HTTP Request
GET https://<base_url>/opg/v1/charges/<charge_id>
Responses
| Status | Meaning | Description |
|---|---|---|
| 200 | OK | Charge fetched |
| 4xx or 5xx | Error | Please check the error table. |
Create a Mock Charge Transaction
curl -XPOST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
"https://<base_url>/opg/v1/charges/34vgGPAKbVTMmmsiyy3S7LYihDB/transactions" \
-d '{
"status": "accepted"
}'
The call above return HTTP status 200 if the Mock Charge Transaction was successfully created
HTTP/1.1 200 OK
{
"amount": "123.45",
"id": "2KaABHhzBVa6qFHeJOebJDle8vG",
"mobile": null,
"parent_transaction_id": null,
"pos_id": 123,
"status": "accepted",
"status_datetime": "2023-01-10T08:09:55Z",
"status_reason": null,
"type": "payment"
}
This endpoint is only available on the Sandbox environment and produces a simulated Transaction event, as if originated directly from Multicaixa.
HTTP Request
POST https://<base_url>/opg/v1/charges/<ID>/transactions
URL Parameters
| Parameter | Description |
|---|---|
| ID | The ID of the Charge for which a Transaction will be simulated |
Payload
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | true | Desired simulated transaction status, either accepted or rejected |
| status_reason | integer | false | If status is rejected, a status reason must be set. |
Responses
| Status | Meaning | Description |
|---|---|---|
| 201 | Created | Mock charge transaction created |
| 409 | Conflict | The mock transaction could not be created due to invalid charge status |
The response structure is described here.
Charge Schema
The following table details the charge schema:
Schema
| Attribute | Type | Nullable | Max. Length | Comments |
|---|---|---|---|---|
| amount | string | false | 18 | Amount of the requested charge in a string format. Use (.) as decimals separator; at most 2 decimal places |
| created_at | string | false | Set to ISO8601 datetime the charge was created | |
| deeplink | string | true | A custom URI scheme (mcxwallet://) to be opened directly on the client's mobile device — either from a mobile browser or embedded in an app. Opening this URI launches the Multicaixa Express app to complete the payment |
|
| deeplink_redirect | string | true | An HTTPS URL that redirects to the deeplink when opened on a mobile device. Use this when you need a standard web link (e.g. in an SMS, email, or web page) that will trigger the Multicaixa Express app on the client's device | |
| expires_at | string | false | Set to ISO8601 datetime at which the charge will expire if not used | |
| id | string | false | 30 | Unique identifier of the charge |
| pos_id | integer | false | The POS_ID assigned by EMIS used to create the charge | |
| qrcode_url | string | false | A URL that returns the QR code as a PNG image (with Content-Type: image/png). Can be embedded directly in an <img> tag or displayed to the client for scanning with Multicaixa Express |
|
| status | string | false | One of [active, expired, used] |
|
| transaction_id | string | true | 30 | If used a transaction_id will be set, otherwise will be null |
Status expired and used are final status.
Sandbox
When using the sandbox environment please check information below.
Testing
Mobile
The mobile number you pass for the requests will determine the final status of the transaction.
| Mobile | Status | Reason | Details |
|---|---|---|---|
| 900000000 | accepted | N/A | Simulates an accepted transaction by the client. Occurs 5 to 20 seconds after request. |
| 900002004 | rejected | 2004 | Simulates the timeout incurred when the client receives the payment request but fails to accept within the time limit. Occurs 90 seconds after request. |
| 900003000 | rejected | 3000 | Simulates a client refusal. Occurs in 5 to 20 seconds after request. |
| 9XXXXXXXX | rejected | 2010 | Simulates a refusal from the processor. Occurs immediately after request. |