Accept payments
The Velocity payments API takes an opinionated approach that forces the integrating client to package payment activity around orders. This may seem unnecesarily tedious for simple transactions, however, you will find benefits to the approach once you start making complex payments such as multi-currency and split payments. In addition, further benefits are evident when tracking activity on the admin dashbaord as well as when incorporating other payment modes such as POS devices and MoMos.
Payment flow
Create Sales Order
A sales order represents the intention to make a payment for a list of items. It is possible to create a detailed list of products that you sell in the admin dashboard and then use the respective item codes in this payload, see API Reference for more on fetching items.
For ad-hoc payments not mapped to specific products, create a generic item in the admin portal and use the respective item code in the payload.
Request
curl --request POST --location 'https://api.velocityafrica.net/sales-orders' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: ...' \
--data '{
"currencyCodeString": "USD",
"orderDate": "2026-05-06",
"dueDate": "2026-05-13",
"notes": "Thank you for your business",
"authorized": true,
"items": [
{
"itemCode": "{{itemCode}}",
"qty": {{qty}},
"unitPrice": {{unitPrice}},
"amount": {{amount}}
}
],
"charges": [
{
"amount": {{charge}}
}
]
}'
You can use the charges collection to add line items that will be added to the order total.
Response
{
"state": "salesOrder",
"status": "manual",
"body": {
"paidAmount": 0.0,
"changeAmount": 0.0,
"outstandingAmount": 5.0,
"trace": "187fcd44-1007-433f-9097-6f3786e8e63a",
"authorized": true,
"name": "SORD-00038",
"grandTotal": 5.0,
"status": "UNPAID"
},
"workflowId": "616",
"externalId": "187fcd44-1007-433f-9097-6f3786e8e63a"
}
Fields
| Field | Type | Description |
|---|---|---|
| itemCode | String | Item code (can be fetched once and cached) |
| qty | Integer | Number of items (of of type itemCode) |
| unitPrice | Float | Price of a single item |
| amount | Float | qty × unitPrice total |
| charge | Float | Additional amounts (tax, surcharges, etc.) |
Initiate payment
Payment is always made against a sales order. Payments can be authorized via WEB (redirect to website) or REMOTE (USSD push). Part payments are allowed.
Request
curl --request POST --location 'https://api.velocityafrica.net/transactions' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: ...' \
--data '{
"amount": {{amount}},
"paymentProcessorLabel": "{{paymentProcessorLabel}}",
"debitPhone": "{{debitPhone}}",
"debitRegion": "ZW",
"debitCurrency": "{{debitCurrency}}",
"debitRef": "{{debitRef}}",
"type": "REQUEST",
"authType": "{{authType}}",
"salesOrderId": "{{salesOrderId}}"
}'
Response
{
"state": "gatewayPayment",
"status": "manual",
"body": {
"id": "7fa6c3d1-cf54-491c-ac1f-84be77a97e5a",
"trace": "e49f39bb-14da-48e0-bd19-1665761b43ba",
"amount": 5,
"paymentStatus": "SUCCESS",
"pollStatus": "PENDING"
},
"workflowId": "617"
}
There are quite a few status fields that track different event types. The relevant one to know if a payment initiation was successful is the body.paymentStatus field.
The only exception to this is with VMC (Visa/Mastercard) payments. The paymentStatus field returns as PENDING until a customer successfully completes a checkout on the redirected page. This effectively means that a PENDING response is fine for VMC. You can proceed to polling to verify the transaction outcome.
Fields
| Field | Type | Description |
|---|---|---|
| amount | String | Outstanding amount from the order (can be partial) |
| paymentProcessorLabel | Enum | ECOCASH, VMC, CASH, WALLET |
| debitCurrency | Enum | USD, ZWG |
| debitPhone | String | Phone number of customer to be charged (for ECOCASH & WALLET this is the account to be charged) |
| debitRef | String | Unique reference from client system |
| authType | Enum | WEB (VMC only), REMOTE |
| salesOrderId | String | Sales order ID from previous step |
On paymentProcessorLabels, VMC is for Visa/Mastercard transactions. WALLET is for closed loop wallet programmes
Poll Payment
Use the transaction trace UUID from the previous response to poll for the final payment result. You can repeat the payment → poll cycle as many times as necessary to bring the outstanding amount to zero.
Request
curl --request PUT --location 'https://api.velocityafrica.net/transactions/poll/{{transactionTrace}}'
Response
{
"state": "done",
"status": "finished",
"body": {
"id": "7fa6c3d1-cf54-491c-ac1f-84be77a97e5a",
"trace": "e49f39bb-14da-48e0-bd19-1665761b43ba",
"amount": 5.0,
"paymentStatus": "SUCCESS",
"pollStatus": "SUCCESS"
},
"workflowId": "617"
}
Use the body.pollStatus field to determine whether the transaction succeeded.
Update Sales Order
Once the outstanding amount reaches zero, update the sales order to ensure all necessary accounting entries are completed. Upon success, you will have completed a single sale lifecycle.
Request
curl --location --request PUT 'https://api.velocityafrica.net/sales-orders/update-workflow/{{salesOrderTrace}}'
Response
{
"state": "done",
"status": "finished",
"body": {
"salesOrder": {
"id": "625b63f6-0acb-40d7-b971-5a970a6346df",
"paidAmount": 5.0,
"outstandingAmount": 0.0,
"status": "PAID",
"name": "SORD-00038"
},
"invoice": {
"id": "0bd9c7c1-e5f5-478d-8048-7d4a427aed74",
"name": "SINV-00017",
"status": "COMPLETE"
}
}
}
Use the body.salesOrder.status field to confirm the order has been fully paid. A PAID status indicates successful completion.