Pewang USSDIntegration guide
Plug-and-go

One webhook for your USSD

Point your USSD extension at a single fixed URL. Every time someone uses your short code, your provider sends a POST here with session and input fields. You return CON or END plus the line of text the phone should show. Implement production menus in lib/ussd/handler-product.ts. This page is the contract your engineers need; bookmark ussd.pewang.company.

Assumption: one fixed callback URL. Branch on serviceCode if you add more short codes later. Demo vs production: the IBC-style test menu lives in lib/ussd/handler-demo.ts and runs on local dev and Vercel Preview by default. On Vercel Production the demo is off unless you set USSD_USE_DEMO_MENU=true — live traffic uses lib/ussd/handler-product.ts only, so the demo does not affect your real launch unless you opt in.

Callback URL (set once in the provider dashboard)

Example path in the portal: Shared USSD Update Extension → callback / webhook URL. Use exactly:

https://pewang.company/api/ussd

POST only for live traffic. Body: HTML form fields (not JSON). Must be reachable over the public internet with HTTPS.

Discovery (optional)

GET https://pewang.company/api/ussd returns JSON with the same field names and CON/END rules — useful for gateways or internal tooling, not for handsets.

Request body (form fields)

Content type: application/x-www-form-urlencoded or multipart/form-data.

FieldMeaningExample
sessionIdUnique id for this USSD session (required).88867484
networkCodeMobile network identifier from the carrier.1
serviceCodeThe short code the subscriber dialled.*657*7778#
phoneNumberSubscriber MSISDN (required).254722002222
textAll inputs so far, separated by *. Empty on the first hit.11*1234 1*1234*5000

Most flows only need to split text on * to know which step you are on. Use sessionId if you add server-side session storage (for example Redis).

Response (plain text)

One line of plain text. It must start with CON or END, a space, then the user-visible message.

PrefixWhen to useExample
CONShow another menu or ask for more input.CON Enter your PIN
ENDClose the session (success or final message).END Thank you. Goodbye.

In this codebase you return { continueSession, message } from handleUssd; the HTTP route adds CON or END for you.

Where you plug in your product

  • lib/ussd/handler-product.tsproduction menus, validation, calls to your APIs or database.
  • lib/ussd/handler-demo.ts — IBC-style sample menu for testing only (not used in Vercel production by default).
  • lib/ussd/handler.ts — chooses demo vs product from VERCEL_ENV and USSD_USE_DEMO_MENU.
  • lib/ussd/types.ts — inbound/outbound types.
  • app/api/ussd/route.ts — parses the form POST and formats the reply.
  • lib/ussd/public-config.ts — fixed public callback URL and doc constants.

Optional Bearer token

If the server has USSD_WEBHOOK_SECRET set, every POST must include Authorization: Bearer <same value>. Only turn this on if your provider can send that header. If it is unset, only the URL and TLS protect the endpoint.

Try it with curl (local)

First screen — empty text:

curl -sS -X POST 'http://localhost:3000/api/ussd' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'sessionId=demo-1' \ --data-urlencode 'networkCode=1' \ --data-urlencode 'serviceCode=*657*7778#' \ --data-urlencode 'phoneNumber=254722002222' \ --data-urlencode 'text='

Change text to 1 or 1*next to simulate later steps. The sample uses serviceCode *657*7778#; your gateway will send the real code your extension uses.

To hit production, use the same form body but POST to https://pewang.company/api/ussd.

Repo reference: docs/DEVELOPER.md. Hosting and deploy commands for operators: docs/VERCEL-CLI.md (not required to integrate with the webhook).