USSD application
Build a USSD banking menu that checks a balance and pays a bill through your API, handles failures and texts a receipt.
In this guide you build a small USSD banking service. A customer dials your short code, checks their balance, or pays a bill after confirming the amount, and gets an SMS receipt. Your own API holds the accounts; Buni.ai runs the menus and calls it.
If you have not built a USSD flow before, do the USSD quickstart first. This guide assumes you know how to add Menu nodes, switch them to the structured editor and connect options.
The finished flow:
Start (Main): 1. Check balance 2. Pay a bill
1 → API "Get balance"
success → Balance (Last): "Your balance is ..."
failure → Unavailable (Last)
2 → Bill account (Intermediary, open prompt)
→ Bill amount (Intermediary, open prompt)
→ Router "Valid amount?"
amount 1–100000 → Confirm (Intermediary): 1. Pay 2. Cancel
1 → API "Pay bill"
success → Messaging (SMS receipt) → Paid (Last)
failure → Unavailable (Last)
2 → Cancelled (Last)
fallback → Invalid amount (Last)Before you start, you need:
- A USSD project. Go to Projects, select New project, set Channel to USSD and choose your USSD provider.
- An HTTPS API with two endpoints, for example
GET /accounts/{phone}/balancereturning{ "amount": 1520.50 }andPOST /paymentsreturning{ "reference": "PAY-1234" }. - Your API key saved as a credential, for example
BANK_API_KEY. - An SMS provider account (Twilio, Africa's Talking or Infobip) for the receipt, with its credential saved.
Build the main menu
Open the Start node (the Main menu every session begins at). In the structured editor, set Menu Message to Kijani Bank and add two options: Check balance and Pay a bill.
Add the other nodes from the table below before you connect anything, so the destination pickers can find them.
| Name | Node | Settings |
|---|---|---|
| Get balance | API | See the next step |
| Balance | Menu | Menu Type Last |
| Unavailable | Menu | Menu Type Last, message Service unavailable. Please try again later. |
| Bill account | Menu | Intermediary, message Enter bill account number:, no options, Output variable billAccount |
| Bill amount | Menu | Intermediary, message Enter amount:, no options, Output variable billAmount |
| Valid amount? | Router | See step 3 |
| Confirm | Menu | Intermediary, options Pay and Cancel |
| Invalid amount | Menu | Last, message That amount is not valid. Please dial again. |
| Cancelled | Menu | Last, message Payment cancelled. |
| Pay bill | API | See step 4 |
| Receipt | Messaging | See step 5 |
| Paid | Menu | Last |
Point option 1 of Start at Get balance and option 2 at Bill account. Connect Bill account to Bill amount, and Bill amount to Valid amount?.
Fetch the balance
Open Get balance:
- Set the method to GET and the URL to
https://api.example.com/accounts/{{state.currentUserSessionSystemContext.phoneNumber}}/balance. That variable holds the caller's number in the format your aggregator sends. - Under Auth or the headers, send your key as
Bearer ${CREDENTIALS.BANK_API_KEY}. - Set Output variable to
balance. The response body is saved there, so the amount is{{state.balance.amount}}. - Turn on Retry with 1 or 2 attempts and a short delay. The caller is waiting on the line.
- Leave Failure path on. Connect success to Balance and failure to Unavailable.
Set the Balance message to Your balance is KES {{state.balance.amount}}. Thank you for banking with us.
Check the amount
Open Valid amount? and add a path where state.billAmount is greater or equal 1 and less or equal 100000. Turn on the Fallback path, which catches anything else, including text that is not a number.
Connect the first path to Confirm and the fallback path to Invalid amount.
Set the Confirm message to Pay KES {{state.billAmount}} to account {{state.billAccount}}?, then point option 1 (Pay) at Pay bill and option 2 (Cancel) at Cancelled.
Make the payment
Open Pay bill:
- Set the method to POST and the URL to
https://api.example.com/payments, with the same authorization header as before. - Set a JSON body:
{
"phone": "{{state.currentUserSessionSystemContext.phoneNumber}}",
"account": "{{state.billAccount}}",
"amount": "{{state.billAmount}}"
}- Set Output variable to
payment. - Leave Retry off unless your API accepts an idempotency key. Retrying a payment request can charge the customer twice.
- Connect success to Receipt and failure to Unavailable.
Text a receipt
Open Receipt (a Messaging node):
- Choose your provider under Medium and its credential under Credentials to connect with.
- Set Resource to SMS and Operation to Send.
- Set From to your sender ID, To to
{{state.currentUserSessionSystemContext.phoneNumber}}, and Message toPaid KES {{state.billAmount}} to {{state.billAccount}}. Ref {{state.payment.reference}}. - Connect it to Paid, and set the Paid message to
Payment successful. Ref {{state.payment.reference}}. An SMS receipt is on its way.
See Messaging for provider details. The SMS is sent before the closing screen appears, so keep the flow short.
Test in the simulator
Save the project, select Simulate and dial. Try both menu options, an amount of 0, a non-number, and Cancel on the confirmation. To test the failure path, point Get balance at a URL that returns an error and check you land on Unavailable.
Go live
Publish the project and paste its Callback URL into your aggregator's dashboard. See Going live.
Check that it worked
- Option 1 shows the balance from your API, or Unavailable when the API fails.
- Option 2 asks for the account and amount, rejects amounts outside the range, and asks for confirmation before paying.
- A successful payment ends on Paid and an SMS with the reference arrives on the dialling phone.
- Every closing screen ends the session: the simulator shows no reply field.
Troubleshooting
Next steps
Support bot with human handoff
Build a chatbot that answers from your knowledge base with an AI Agent and hands the conversation to a person in the Inbox when asked.
Appointment reminder calls
Call customers the day before their appointment from a scheduled automation, and know what a Voice (IVR) project can and cannot do for outbound calls today.