Skip to main content

Payment Intent Overview

Every payment in the H2H API starts with a payment intent. You create one by calling the same endpoint every time. What happens next depends on what you send.

One endpoint, two modes​

ModeWhenWhat you get backWhat to do next
Direct chargeYou send network + account_numberA charge response with next_stepFollow the Direct Charge guide
StandardYou leave out network and account_numberA PaymentIntent objectComplete the payment via hosted checkout or your chosen payment method

The rule is simple: sending both network and account_number triggers a direct charge. If either is missing, you get a standard payment intent.

Most H2H partners use direct charge. Use standard mode when you do not know the customer's mobile money details yet, or when you want the customer to choose how to pay.

Creating a payment intent​

curl -X POST "https://api.modempay.com/h2h/v1/payments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"amount": 10,
"currency": "GMD",
"network": "afrimoney",
"account_number": "7012345",
"external_reference": "order-1042",
"metadata": { "customer_id": "cus_889" },
"callback_url": "https://yourapp.com/payments/callback"
}
}'

Request fields​

FieldRequiredMeaning
amountYesAmount to charge the customer
currencyYesCurrently "GMD"
networkFor direct chargeMobile money network: "wave", "afrimoney", "qmoney", or "aps"
account_numberFor direct chargeThe customer's mobile money number, in local format
external_referenceNoYour own reference (order ID, invoice number). Use it to match Modem Pay payments to records in your system
metadataNoKey-value pairs you want stored with the payment. Returned to you in responses and webhooks
callback_urlNoURL where Modem Pay sends the result of this payment
return_urlNoWhere the customer is sent after completing payment (standard mode / hosted flows)
cancel_urlNoWhere the customer is sent if they cancel (standard mode / hosted flows)

Tip: Always send external_reference. When you are reconciling payments later, it is much easier to search by your own order ID than by Modem Pay's IDs.

The response depends on the mode​

Direct charge response​

If you sent network and account_number, the response is a charge response with a transactionId and next_step telling you how to complete the payment. This is covered fully in the Direct Charge guide — read it before going live.

Standard response: the PaymentIntent object​

If you did not trigger a direct charge, you get a PaymentIntent object.

The fields you will use:

FieldMeaning
idUnique ID of the payment intent. Save it
amount, currencyEcho of what you requested
type_of_payment"regular" or "direct" — which mode this intent used
is_sessiontrue means the customer completes payment through a hosted session
callback_urlThe URL you provided for result notifications
external_referenceYour reference, echoed back
metadataYour key-value pairs, echoed back

Fields for your records:

FieldMeaning
business_id, account_idThe partner business and account this payment belongs to
transaction_feeThe fee applied to this payment
usd_fx_rateExchange rate, present when currency conversion is involved
payment_metadataSystem-generated context about the payment
customer_nameCustomer name, when available for the flow used
payment_link_idPresent when the intent came from a payment link

You do not need most of the record fields to complete a payment. They exist for reporting and reconciliation.

Payment Intent Resumption and Duplicate Request Handling​

Modem Pay may resume an existing PaymentIntent when a new payment request matches an existing non-completed transaction.

This behavior is designed to prevent duplicate payment intents from being created when the same payment is submitted or retried within a short period.

How matching works​

A new payment request may be matched to an existing PaymentIntent when the following conditions are met:

  • The merchant account is the same
  • The payment network/method is the same
  • The account/payment details are the same
  • The payment amount is the same
  • The existing PaymentIntent has not reached completed status
  • The existing PaymentIntent falls within the applicable matching window

When a match is identified, Modem Pay may return the existing PaymentIntent instead of creating a new one.

The response may indicate that an existing PaymentIntent was resumed.

Important: A resumed PaymentIntent is not a new payment​

A resumed request does not create a second underlying payment transaction.

The payment_intent_id and Modem Pay transaction reference remain associated with the original PaymentIntent.

Merchants must therefore not treat every API response as a new payment order or create a new internal order solely because the same PaymentIntent is returned again.

Webhooks​

Modem Pay sends the relevant webhook when the PaymentIntent reaches the applicable status.

Multiple requests that resolve to the same PaymentIntent do not result in multiple completed-payment webhooks for that PaymentIntent.

Merchants should use the payment_intent_id and/or Modem Pay transaction reference to identify and reconcile the payment before crediting an order or customer account.

Merchant-side idempotency and reconciliation​

Merchants are responsible for maintaining unique internal order references and implementing appropriate idempotency checks when processing payment responses and webhooks.

We strongly recommend that merchants:

  1. Assign a unique order or transaction reference to every customer order.
  2. Store the Modem Pay payment_intent_id against the corresponding order.
  3. Check whether a PaymentIntent already exists in their system before creating or crediting another order.
  4. Treat repeated responses containing the same PaymentIntent as updates to the existing payment rather than automatically creating a new payment.
  5. Process payment completion based on the PaymentIntent's confirmed status and webhook events rather than solely on the receipt of an API response.
  6. Use external_reference to provide their own unique order identifier. While external_reference is optional, its use is strongly recommended for reliable reconciliation and idempotency.

Example​

If a merchant submits the same payment request twice and both requests match the same existing PaymentIntent:

Request 1

PaymentIntent: pi_123
Status: pending

Request 2

PaymentIntent: pi_123
Status: pending
Resumed: true

The second response does not represent a second payment.

The merchant should continue tracking the original order against pi_123 rather than creating a second order solely because a second API request was made.

Responsibility for order creation and crediting​

Merchants remain responsible for ensuring that their internal order, wallet, account or customer balance is credited only once for a given payment.

Receiving a payment response does not by itself establish that a new or separate underlying payment has been completed.

Merchants should always reconcile the PaymentIntent status and associated transaction reference before releasing goods, services, funds or customer credit.

For the most reliable implementation, merchants should maintain the following relationship:

Merchant Order
↓
Unique external_reference
↓
Modem Pay PaymentIntent
↓
Provider Transaction
↓
Completion Webhook

The merchant's internal order ID, external_reference, PaymentIntent ID and provider transaction should be stored and reconciled as part of the merchant's payment lifecycle.

Flow summary​

  1. You create a payment intent (POST /h2h/v1/payments)
  2. Modem Pay validates the request and your authorization
  3. If network + account_number were sent → a direct mobile money charge starts. Follow Direct Charge
  4. If not → you get a PaymentIntent. Complete it through the hosted session or your chosen method
  5. The final result reaches you through your callback_url or webhooks

In both modes, the same rule from the Direct Charge guide applies: a payment is only successful when its status is completed.