Skip to main content

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 showsYou callCovered below
auth_mode: "confirm", openExternal: falsePOST /payments/confirm/:referenceConfirm endpoint
openExternal: trueNothing, the payment completes on its ownRedirect payments
auth_mode: "pin"POST /payments/finalize/:referenceFinalize 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:

  1. Show the customer the confirmation instructions
  2. Show an "I have paid" button
  3. When they tap it, call this endpoint
  4. 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:

  1. Send the customer to launch_url
  2. The customer authorizes on the provider's page or app
  3. 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 value
  • status: "processing" or "pending" → not final yet. Keep verifying via status or webhook
  • status: "failed" → check failure_reason and 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

FieldMeaning
statuspending, processing, completed, failed, cancelled, or abandoned. Only completed is success. See payment statuses
failure_reasonWhy the payment failed. Show it (or a friendly version of it) to the customer
paid_dateWhen the payment completed
amount, currency, transaction_feeThe money details for this payment
payment_accountMasked customer account, safe to show on receipts
test_modetrue means this is a test payment. Never deliver real value for test payments
metadataThe 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

  • :reference is always the transactionId from 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