Buni.aiDocs

Workflow examples

Small, complete flows you can copy with their node settings, from a USSD balance check and money transfer to a form-to-SMS automation.

Each example lists the nodes in order and the settings that matter. Node names in bold are the titles in the node palette. For a full walkthrough of any of these patterns, follow the linked guide.

All examples read values with double-brace templates such as {{state.amount}}. See Variables and templating.

Balance check (USSD)

The caller picks an option and sees their balance from your API.

Menu "Start" (Main) → API "Get balance" → Menu "Balance" (Last)
                           └ failure → Menu "Unavailable" (Last)
NodeSettingValue
Menu "Start"Menu TypeMain
Menu Message / optionsMyBank, option Check balance pointing at Get balance
Api "Get balance"Method, URLGET https://api.mybank.example/accounts/{{state.currentUserSessionSystemContext.phoneNumber}}/balance
HeaderAuthorization: Bearer ${CREDENTIALS.MYBANK_API_KEY}
Output variablebalance
Failure pathOn, failure to Unavailable
Menu "Balance"Menu Type, messageLast, Your balance is KES {{state.balance.amount}}

The full version, with bill payments and an SMS receipt, is in USSD application.

Money transfer with confirmation (USSD)

The caller enters a recipient and an amount, confirms, and gets an SMS receipt.

Menu "Recipient" → Function "Clean number" → Router "Valid number?"
   valid    → Menu "Amount" → Menu "Confirm"
   fallback → Error → back to "Recipient"
Menu "Confirm": 1 Confirm → API "Send money" → Messaging (SMS) → Menu "Sent" (Last)
                2 Cancel  → Menu "Cancelled" (Last)
NodeSettingValue
Menu "Recipient"Type, message, Output variableIntermediary, Enter recipient phone number:, no options, recipient
Function "Clean number"Code, Output variableThe code below, recipientPhone
Router "Valid number?"Path (Expression), Fallback path/^254[17][0-9]{8}$/.test(String(state.recipientPhone)), fallback on
ErrorError MessageEnter a number like 0712345678. Connect it back to Recipient.
Menu "Amount"Type, message, Output variableIntermediary, Enter amount (KES):, no options, amount
Menu "Confirm"Message, optionsSend KES {{state.amount}} to {{state.recipientPhone}}?, options Confirm and Cancel
Api "Send money"Method, URL, bodyPOST https://api.mybank.example/transfers, JSON body with to, amount and from from state
Output variable, Failure pathtransfer, on, failure to an "Unavailable" Last menu
MessagingResource, To, MessageSMS, {{state.currentUserSessionSystemContext.phoneNumber}}, Sent KES {{state.amount}} to {{state.recipientPhone}}. Ref {{state.transfer.reference}}

The Function node turns 0712 345 678, +254712345678 and 254712345678 into the same form, and its return value is saved to recipientPhone:

(input) => {
  const cleaned = String(state.recipient || '').replace(/[\s-]/g, '').replace(/^\+/, '');
  return cleaned.startsWith('0') ? '254' + cleaned.slice(1) : cleaned;
}

Leave Retry off on "Send money" unless your API accepts an idempotency key, so a retry cannot send the money twice.

Registration with a data store (USSD)

Returning callers are greeted by name; new callers register once.

Data Store "Find caller" (GET) → Router "Known?"
  ├ found    → Menu "Welcome back"
  └ fallback → Menu "Your name" → Data Store "Save caller" (POST) → Menu "Registered" (Last)
NodeSettingValue
Data Store "Find caller"Action, Which recordsGET, phone equals {{state.currentUserSessionSystemContext.phoneNumber}}
Output variablecaller
Router "Known?"Path, Fallback pathA condition that the lookup returned a record, fallback on
Menu "Your name"Type, message, Output variableIntermediary, Welcome. Enter your full name:, no options, name
Data Store "Save caller"Action, PayloadPOST, phone and name from state
Menu "Registered"Type, messageLast, Thanks {{state.name}}, you are registered.

Run the flow once and check the Logs tab to see the shape of {{state.caller}} for a known and an unknown number, then write the Known? condition to match it. See Data Store.

Nested menus with Back (USSD)

A main menu with sub-menus that return to it.

Menu "Main" (Main): 1 Services → Menu "Services"   2 My account → Menu "Account"
Menu "Services": 1 Buy airtime  2 Pay bills  0 Back → Main
Menu "Account":  1 Profile      2 Settings   0 Back → Main

Give every sub-menu a way back. In the structured editor you can turn on a Back navigation option on a sub-menu and point it at the parent menu, or add an ordinary option labelled Back whose destination is the parent. Name each menu's Output variable after its level, such as servicesChoice, so you can tell them apart later. See Menus and sessions.

Form submission to SMS (automation)

A hosted web form that stores each submission and texts the person.

Trigger (Form) → Data Store (POST) → Messaging (SMS)
NodeSettingValue
TriggerTrigger SettingsForm, fields full_name (text) and phone (phone), Output variable form
Data StoreAction, PayloadPOST, fields from {{state.form.full_name}} and {{state.form.phone}}
MessagingTo, Message{{state.form.phone}}, Thanks {{state.form.full_name}}, we received your request.

To reply on WhatsApp instead, see Form to WhatsApp message.

Tips

  • Build the main path first, test it in the simulator, then add the error paths.
  • Use descriptive Output variable names such as transfer_amount and balance_result. You will see them again in the variable picker.
  • Store API keys as credentials and reference them as ${CREDENTIALS.NAME}; never paste a key into a node.
  • Use the Documentation tab of a project to note what each part of a large flow does.
Last reviewed 24 September 2026

On this page