Tutorial: PIN login
Two-factor login in a USSD menu: look up the account by the dialling number, check the PIN with your API, and branch on each reply.
What you will build
A balance check behind a login. Your API is called twice: once to find the account for the phone that is dialling, and once to check the PIN the caller types. Each reply decides where the caller goes next.
Welcome back, Ama. Enter your 4-digit PIN:
Your balance is GHS 1,250.40.
| Step | Type | What it does |
|---|---|---|
| Welcome | Entry Point | Offers Check balance or Exit. |
| Find account | Send Data | Looks up the account by {{MobileNumber}}. |
| Registered? | Smart Router | Sends unknown numbers to a sign-up message. |
| Ask for PIN | Ask a Question | Saves the answer as pin. |
| Check PIN | Send Data | Sends the number and PIN; gets the balance back. |
| PIN right? | Smart Router | Shows the balance, or a wrong-PIN message. |
| Four closing screens | End Session | Balance, not registered, wrong PIN, try again later. |
Why this is two factors
{{MobileNumber}} is not typed by the caller. The mobile network supplies it with every request, so it proves the caller is holding that SIM. The PIN proves they know the secret. A stolen PIN is no use from another phone, and a borrowed phone is no use without the PIN.
{{MobileNumber}}, which a step in your app cannot overwrite.What your API needs
1. Find the account
{ "registered": true, "firstName": "Ama" }For a number with no account, answer { "registered": false }, not an error.
2. Check the PIN
{ "phone": "233241234567", "pin": "4821" }{ "verified": true, "balance": "1,250.40" }
{ "verified": false, "attemptsLeft": 2 }Build it
Set up the welcome screen
On the Entry Point, set the text toSika Savingsand the options toCheck balanceandExit.Find the account
Add Send Data. Choose GET and put the caller's number in the address:Under Save From Response, addAddresshttps://api.example.com/accounts/{{MobileNumber}}$.registered→registeredand$.firstName→firstName.Branch on whether they are registered
Add a Smart Router with one branch:registered == true.Ask for the PIN
Add Ask a Question. Text:In the Answer section set Save the answer as toWhat the phone showsWelcome back, {{firstName}}. Enter your 4-digit PIN:pinand Answer type to PIN / Secret, so only digits are accepted.Check the PIN
Add a second Send Data. Choose POST, enter the login address, and set the body:SaveRequest body{ "phone": "{{MobileNumber}}", "pin": "{{pin}}" }$.verified→verifiedand$.balance→balance.Branch on the result
Add a second Smart Router with one branch:verified == true.Add the closing screens
Add four End Session screens:Your balance is GHS {{balance}}.This number has no Sika Savings account. Visit a branch to open one.That PIN is not right. Dial again to retry.We cannot reach your account right now. Please try again later.
Goodbye.for Exit.
Join the steps
| From | To |
|---|---|
| Welcome, option Check balance | Find account |
| Welcome, option Exit | Goodbye. |
| Find account, bottom dot | Registered? router |
| Find account, FAIL | We cannot reach your account right now. |
| Registered? first branch | Ask for PIN |
| Registered? ELSE | This number has no Sika Savings account. |
| Ask for PIN | Check PIN |
| Check PIN, bottom dot | PIN right? router |
| Check PIN, FAIL | We cannot reach your account right now. |
| PIN right? first branch | Your balance is… |
| PIN right? ELSE | That PIN is not right. |
registered field at all, the caller leaves by ELSE, which here is the safe direction: they are not let in.Test it
Open the phone preview
Press Test in the top bar. In Dial from, type the number the test should call from, such as0241234567. Your app receives it as{{MobileNumber}}.A registered number, right PIN
Set Dial from to a number your API knows. Reply1, then the PIN. You should see the balance.Wrong PIN
Dial again and type a wrong PIN. You should see the wrong-PIN message, with no balance.An unregistered number
Change Dial from to a number with no account. You should never be asked for a PIN.A PIN that is not digits
Replyabcdat the PIN question. It is refused before your API is called.
https:// and allow requests from the dashboard (CORS). On a real phone the call comes from Asterisks' servers and CORS does not apply. Testing against your laptop? An ngrok address works: the preview adds the header ngrok asks for.Make it yours
- Another try in the same session: connect the wrong-PIN branch back to the PIN question, and have your API answer
lockedwhen attempts run out so a third branch can end the session. - A menu after login: replace the balance screen with a Choice Menu (balance, mini statement, change PIN). Each option can call your API with
{{MobileNumber}}. - A one-time code by SMS: have your login API send the code, then add another question and Send Data step to check it. The caller may not be able to open an SMS while the USSD session is on screen, so tell them they may need to dial again.
- Login as a reusable piece: publish this app and run it from others with a Sub-flow step, passing
verifiedback out.