Confirm Charge
After you create a direct charge, some networks need one more call from you before the payment can finish. This page is the reference for those calls.
Which call you make depends on the next_step in your charge response:
| Your charge response shows | You call | Covered below |
|---|---|---|
auth_mode: "confirm", openExternal: false | POST /payments/confirm/:reference | Confirm endpoint |
openExternal: true | Nothing, the payment completes on its own | Redirect payments |
auth_mode: "pin" | POST /payments/finalize/:reference | Finalize endpoint |
In every endpoint on this page, :reference is the transactionId from your charge response.
For the full integration walkthrough, see the Direct Charge guide. This page covers the endpoints in detail.
Confirm endpoint
POST /payments/confirm/:reference
Use it when auth_mode is "confirm" and openExternal is false (AfriMoney USSD or in-app approval).
Why this endpoint exists
The customer approves the payment on their own phone. Modem Pay checks with the provider on a schedule, so without your help the payment can take up to ~30 minutes to show as completed. Calling confirm tells Modem Pay to check now.
That is why the recommended flow is:
- Show the customer the confirmation instructions
- Show an "I have paid" button
- When they tap it, call this endpoint
- Then poll the transaction status or wait for the webhook
If the customer never taps the button, the payment still completes on the next scheduled check. Confirm is about speed, not correctness.
Request
curl -X POST "https://api.modempay.com/h2h/v1/payments/confirm/cos-212q2mbrg8z6m" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
Response
{
"message": "ok",
"confirmation": "started"
}
This response means the check has started. It does not mean the payment succeeded. Get the final answer from the transaction status or your webhook.
Redirect payments (no endpoint)
When openExternal is true (Wave), there is nothing to call:
- Send the customer to
launch_url - The customer authorizes on the provider's page or app
- Modem Pay detects the result automatically and sends a webhook
Calling confirm or finalize for a redirect payment is not needed and not supported.
Finalize endpoint
POST /payments/finalize/:reference
Use it when auth_mode is "pin" (QMoney, APS). The customer gives you their PIN/OTP and you submit it.
The charge response includes auth_length, the number of digits to collect. Use it to size your PIN input instead of hard-coding a length.
Request
curl -X POST "https://api.modempay.com/h2h/v1/payments/finalize/cos-212q2mbrg8z6m" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pin": "123456"
}'
Never store the PIN. Send it and forget it.
Response
Finalize returns the full transaction object (same shape as the status endpoint below). The field to check is status:
{
"id": "bb270a1f-e0a4-4a3d-bba6-e4046e09d5d0",
"status": "completed",
"amount": 100,
"currency": "GMD",
"payment_method": "qmoney",
"failure_reason": null,
"...": "..."
}
status: "completed"→ the payment is done. Deliver valuestatus: "processing"or"pending"→ not final yet. Keep verifying via status or webhookstatus: "failed"→ checkfailure_reasonand let the customer retry with a new payment
Checking transaction status
GET /transactions/:reference
Works for every flow. Use it to update your UI after a confirm call, or as your polling method if you do not use webhooks.
curl -X GET "https://api.modempay.com/h2h/v1/transactions/cos-212q2mbrg8z6m" \
-H "Authorization: Bearer YOUR_API_KEY"
Response
{
"id": "bb270a1f-e0a4-4a3d-bba6-e4046e09d5d0",
"payment_intent_id": "05bf5898-de54-480c-b822-64c18e3c1a03",
"amount": 100,
"currency": "GMD",
"status": "processing",
"reference": "RVMGQVWLU",
"transaction_reference": "RVMGQVWLU",
"payment_method": "afrimoney",
"payment_account": ".... 4725",
"failure_reason": null,
"transaction_fee": 0,
"transaction_fee_type": "business",
"test_mode": false,
"paid_date": null,
"requires_auth": true,
"auth_mode": "confirm",
"auth_length": 0,
"openExternal": false,
"launch_url": "",
"metadata": {},
"payment_metadata": {},
"customer_name": null,
"customer_phone": null,
"customer_email": null,
"business_id": "120c298f-b736-49c1-8220-8a8c84a5d8f3",
"account_id": "199bf17b-0b5a-45c0-a5b0-1d29f5f173c9",
"createdAt": "2025-11-10T09:32:55.769Z",
"updatedAt": "2025-11-10T09:32:58.191Z"
}
The fields you will use
| Field | Meaning |
|---|---|
status | pending, processing, completed, failed, cancelled, or abandoned. Only completed is success. See payment statuses |
failure_reason | Why the payment failed. Show it (or a friendly version of it) to the customer |
paid_date | When the payment completed |
amount, currency, transaction_fee | The money details for this payment |
payment_account | Masked customer account, safe to show on receipts |
test_mode | true means this is a test payment. Never deliver real value for test payments |
metadata | The key-value pairs you sent when creating the payment |
The remaining fields (business_id, account_id, payment_metadata, and so on) are for records and reconciliation. You do not need them to complete a payment.
Notes
:referenceis always thetransactionIdfrom your charge response- Confirm and finalize make payments complete faster. The final truth is always the transaction
status - Webhooks send you the same status updates without polling, use them if you can