Building Africastalking USSD Application using Sarufi

Building Africastalking USSD Application using Sarufi

Yeah I know Sarufi is a chatbot platform blah blah, but Do you know that you can also speed up your USSD development by using Sarufi Choice Component, well Now you know and that's what I'm going to be sharing with you in this article.

What to expect  👩🏻‍💻?

I aim for this article to be very practical and at the very end you should know to utilize sarufi to speed up your development, Sarufi helps with three main tasks which are state management, input validations, and data collection during the USSD session so you can focus on the executing your core logic from options chosen by the user.

Data collection is done using sarufi-webhook a feature which allows you to share data with third party APIS or apps

WTH is a State machine? How does it relate to USSD? Sarufi?

Unless you're a pro, you might have some questions based on what I just mentioned above, trust me it's not that complicated 🙇🏻, so to help you understand I asked Bard to explain to me like am 5 about state machine and it came with this;

state machine is a machine that can be in one of a few different states, and can change states based on inputs.

Literally, one of the simplest analogs that you can easily understand is to think of the USSD Application as a state machine that changes from one state to another based on your inputs.

USSD Application as a State Machine 

When you open your MPESA menu on *150*00#, It will render you a state (menu) with a particular menu that you can change based on your input whether you decide to send money or withdraw money or you name it.

What should we build 💡?

Enough talking, Now Let's talk 😉 about the idea we are going to build in this article, It could be anything, because of my love for adventure and traveling Let's build an oversimplified travel USSD Application that helps people book tours, Here is how flow diagram our application;

Safari Choice flow

The flow chart diagram is over simplified to make it easy for you to grasp the concept, just know I could make this I as complicated as it can be

What will you need? 🛠

For you to able to successfully follow through this article you will need to have the following things;

  1. Sarufi API Key
  2. Africas talking Account
  3. Sarufi Python Package
  4. FastAPI and Uvicorn

To get your sarufi API Key please go to your sarufi dashboard ==> Profile ==> Authorization

Installing package

You can easily the packages using a pip

pip install sarufi 
pip install fastapi
pip install uvicorn
pip install python-multipart
Installing dependencies

Newbie? In this article I'm not going to cover A-z about what is sarufi or what is intents or flow, If you're totally new to sarufi please take 10 minutes to familiarize yourself with the documentation.  Don't worry much, you might still get it if you gonna pay attention as it's just a basic JSON/YAML.

Entry Point for USSD Application

Every Chatbot you create with sarufi must have at least an entry point which can simply be intent with intent phrases/examples and then from there, the state machine will take control to guide the user through their designed menu based on their inputs.

Let's call our entry point intent salamu and then sample phrases that are not really necessary since it's only one.

entry_intent = {
   "salamu":[
      "Hi",
      "Hello",
      "Mambo",
      "Niaje",
      "Nighani",
      "Mambo Vipi"
   ]
}
Defining our entry point intent

The entry point triggers an entry state (menu), Please note that the entry_intent name must resemble the entry_state name, in our case based on our draft diagram, It's going to be as shown below;

entry_state = {
   "salamu":{
      "message":[
         "1. Book Safari",
         "2. Save to Travel",
         "3. Cancel"
      ],
      "next_state":"end"
   }
}
Defining our entry point state

🧐 See easy-peasy, if you noticed in the entry state, there is key for next_state which what controls the state machine

Still don't get it? You can learn more about next_state on sarufi documentation

Let's finish the whole thing

Now that we know what the next state is we can finish our entire state flow logic so that we can integrate with FastAPI and use it as a webhook for our USSD Application, Once you finish your flow it might look something like this;

Wait a minute, I forgot to mention the choice component which basically at as a switch case between input from the user and the state component, I believe you can still get it by following the below JSON structure.

entry_state = {
    "salamu": {
        "message": ["1. Book Safari", "2. Save to Travel", "3. Cancel"],
        "next_state": "choice_entry_menu",
    },
    "choice_entry_menu": {
        "1": "book_safari",
        "2": "save_to_travel",
        "3": "cancel_safari",
    },
    "book_safari": {
        "message": ["1. NgoroNgoro", "2. Serengeti", "3. Uduzungwa"],
        "next_state": "safari_siku_ngapi",
    },
    "safari_siku_ngapi": {
        "message": ["Ungependa Safari ya siku ngapi ?"],
        "next_state": "safari_ingiza_pin",
    },
    "safari_ingiza_pin": {
        "message": ["Safari yako ni Shilling 50000/=", "Ingiza PIN yako kuthibitisha"],
        "next_state": "safari_paid",
    },
    "safari_paid": {
        "message": [
            "Hongera Umelipia Safari yako ya siku {{safari_ingiza_pin}} kwa Shilling 50000/="
        ],
        "next_state": "end",
    },
    "save_to_travel": {
        "message": ["Je ungependa kusave kiasi gani ?"],
        "next_state": "save_to_travel_pin",
    },
    "save_to_travel_pin": {
        "message": [
            "Tafadhali ingiza PIN yako kuthibitisha",
        ],
        "next_state": "save_to_travel_paid",
    },
    "save_to_travel_paid": {
        "message": [
            "Hongera umeweza kusave Shilling {{save_to_travel_pin}} , tunakutakia safari njema"
        ],
        "next_state": "end",
    },
    "cancel_safari": {
        "message": ["Sawa, Karibu tena Safari Choice"],
        "next_state": "end",
    },
}
Safari Choice Entire flow

The next part is adding the trigger intent and flow to the sarufi chatbot and then we can use Sarufi Playground to test if your chatbot is working as expected, you can easily do this as shown below;

>>> from sarufi import Sarufi
>>> sarufi = Sarufi( api_key="your Sarufi API Key")
>>> mybot = sarufi.create_bot(
    name="safari choice",
    description="Safari Choice helps you book a safari or save for a safari",
    intents=entry_intent,
    flow=entry_state,
)
>>> print(mybot)
Bot(id=1562, name=safari choice)
Creating our chatbot using Sarufi

Testing Our Chatbot

By default, the visible to-community parameter is true you can explicitly set it to false if you want to, If you did as did above should be able to see it in the Sarufi Playground.

0:00
/

FastAPI Sarufi Callback

Kudo to you if you managed to follow through with this step, one of our very last steps is creating a very simple FastAPI callback that will be called by the Africastalking  USSD application to request for the menu to be rendered to the user.

import os
from sarufi import Sarufi
from fastapi import FastAPI, Form
from fastapi.responses import PlainTextResponse


app = FastAPI()
sarufi = Sarufi('Your Sarufi API Key')
safari_choice = sarufi.get_bot(1562)


def respond(session_id, phone_number, message):
    chat_id = f"{session_id}-{phone_number}"
    message = message.split("*")[-1]
    sarufi_response = safari_choice.respond(chat_id=chat_id, message=message)
    sarufi_message = "\n".join(sarufi_response.get("message"))
    if sarufi_response.get("next_state") != "end":
        ussd_message = f"CON {sarufi_message}"
    else:
        ussd_message = f"END {sarufi_message}"
    print(ussd_message)
    return ussd_message


@app.get("/")
def index():
    return {"who am I ?": "I'm a ussd application that helps you book a safari"}


@app.post("/ussd")
def ussd(
    text: str = Form(None),
    sessionId: str = Form(None),
    phoneNumber: str = Form(None),
    serviceCode: str = Form(None),
):
    print(text, sessionId, serviceCode, phoneNumber)
    if text in [None, "", "default"]:
        return PlainTextResponse(
            respond(sessionId, phoneNumber, message="dummy greeting"),
            media_type="text/plain",
        )
    else:
        return PlainTextResponse(
            respond(sessionId, phoneNumber, text), media_type="text/plain"
        )


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=80)

You can easiy run this code by forking a repl on replit, don't forget to add SARUFI_API_KEY to your secrets, If you gonna use this approach

Also if you noticed the line safari_choice = sarufi.get_bot(1562)` sounds strange but basically what it does is get getting a chatbot we just created using its ID, the output is Bot Object.

Running our Callback

You can run your FastAPI callback either by invoking it directly or using uvicorn as shown below and use your URL as a callback to Africastalking USSD App, make sure it's available on the internet, To ensure I recommend going with replit and then if replit fails you can try ngrok.  

# Do this 
python3 app_name.py 

# OR this 
uvicorn app_name:app 

If you're using Replit you should see something as shown below;

Now the most important URL we need is https://africas-talking-sarufi-callback.neurotechafrica.repl.co/ussd since It's the one with the post method for executing the USSD logic, now the next step is to your Africas talking portal and add it as a fall back as shown below;

0:00
/

Hayawi Hayawi Sasa Yamekuwa (Swahili), If you managed to read through the entire article you deserve some snacks 🍿🍬 , I wanted the article to be short and this is short I could manage, I hope you enjoyed the article, don't forget to share it with your network.

By the way, This article is me finally beating procrastination, It has been quite a while since I last published an article, and I plan to keep writing frequently, so keep an eye on this blog, cheers🥂.