USSDK

Handling Payments

USSDK does not process payments for your USSD application. You bring your own payment provider and use hooks to trigger payments through your API.

For example, you may want users to pay for an event, settle a bill, or make a donation. USSDK handles the screens and collects their responses. Your API calls the payment provider to collect the money.

Let's look at how you can do this with Paystack. You'll need your own Paystack account and an API endpoint for your hook.

Set up the steps

For this example, we'll collect GHS 10 for an event using mobile money in Ghana. You can build this flow:

StepMessageKey
EmailEnter your email addressemail
NetworkChoose your mobile money networkpayment_network
ConfirmationPay GHS 10 using this phone number?confirmation
Payment request{{message}}Leave empty

For the network menu, use these key aliases:

Menu itemKey alias
1) MTNmtn
2) Telecelvod
3) ATMoneyatl

These are Paystack's Ghana mobile money provider codes. See its mobile money guide for other supported networks and countries.

On the confirmation step, connect 1) Pay to the payment request step and 2) Cancel to a separate ending step.

Set the hook on the payment request step to your endpoint:

https://api.example.com/payments/start

Leave its menu empty and custom input disabled so the session ends after displaying the response.

Hooks run before a step is displayed. Place the payment hook after confirmation so it only runs when the user chooses to pay.

Call Paystack from your hook

Your endpoint receives the usual hook request. The details you need are:

  • props.session.msisdn: the user's phone number.
  • props.values.email: the email they entered.
  • props.values.payment_network: the network alias they selected.

Use the selected network for Paystack, rather than props.session.network.

Users will usually pay with the same phone they're using for the USSD session. Give that session time to close before triggering the payment prompt: return the hook response immediately, then call Paystack after a short delay.

Here's the Javascript function we'll schedule below. The payment argument contains those details and a reference you've saved for this payment.

async function requestPayment(payment) {
  const response = await fetch('https://api.paystack.co/charge', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: payment.email,
      amount: '1000', // 1000 pesewas = GHS 10
      currency: 'GHS',
      reference: payment.reference,
      mobile_money: {
        phone: payment.phone,
        provider: payment.network, // mtn, vod, or atl
      },
    }),
  })

  const result = await response.json()
  if (!response.ok || result.status !== true || !result.data) {
    throw new Error('Could not start payment')
  }

  return result.data
}

Keep your Paystack secret key on your backend. Paystack expects the amount in the currency's smallest unit, so GHS 10 is 1000 pesewas. See the Create Charge API for the full request options.

End the session before requesting payment

In your hook handler, once you've prepared and saved payment, schedule the request and respond to USSDK:

setTimeout(() => {
  requestPayment(payment).catch((error) => {
    // Record the failure against the payment in your backend.
    console.error('Payment request failed', error)
  })
}, 3000)

return res.json({
  message: 'You will receive a payment prompt shortly. Please approve it.',
})

The final step displays {{message}} and ends the USSD session. When the timer fires, your backend calls Paystack so the user can receive the payment prompt on the same phone. The three-second delay is an example; adjust it for your network.

Don't wait for the timer or the Paystack response before responding to the hook. If your hosting platform stops background work after a response, use a delayed job instead of setTimeout.

The hook response has already been sent when the payment request runs, so any later failure must be handled in your backend rather than returned to that USSD session.

Confirm the payment

Starting a payment does not mean the user has paid. Paystack sends a charge.success event to your backend when payment succeeds. Set up a separate endpoint for this using Paystack's webhook guide, then update your payment record using the transaction reference.

The USSDK hook starts the payment; the Paystack webhook confirms the result. This happens outside the USSD session. You can send a receipt through your own SMS service or let users check their payment status the next time they dial your code.

For pending payments, see Paystack's transaction verification guide. Check an existing payment's status before starting another attempt for the same purchase.

You can use the same approach with another payment provider. The provider's API will change, but you still trigger the payment from your own API through a USSDK hook.

On this page