Webhooks are HTTP callbacks that Africa’s Talking sends to your application to notify you about events like SMS delivery, call status, or subscription changes. Your application must expose public endpoints to receive these notifications.
An event happens in Africa’s Talking (e.g., SMS delivered, call ended, user subscribes).
2
Africa's Talking Sends HTTP POST
AT sends an HTTP POST request to your configured webhook URL with event data.
3
Your Application Processes
Your Flask route handler receives and processes the webhook payload.
4
Acknowledge Receipt
Your application must respond with a 200 OK status to confirm receipt.
Always respond with 200 OK quickly. If your endpoint doesn’t respond or returns an error, Africa’s Talking may retry the webhook or mark your endpoint as failing.
Receives incoming SMS messages sent to your shortcode or number.
routes/sms.py
@sms_bp.route("/twoway", methods=["POST"])def twoway_callback(): """ Handle two-way SMS callbacks from Africa's Talking. """ linkId = request.values.get("linkId") text = request.values.get("text") to = request.values.get("to") msg_id = request.values.get("id") date = request.values.get("date") sender = request.values.get("from") if not linkId or not text or not to or not msg_id or not date or not sender: return "BAD", 400 print(f"Received 2-way SMS from {sender}: {text}") # Respond with a new SMS back to the sender send_twoway_sms( message=f'This is a response to: "{text}"', recipient=sender, ) return "GOOD", 200
Payload Example:
{ "linkId": "SampleLinkId123", "text": "Hello, I need help", "to": "12345", "id": "ATSMSid_12345", "date": "2026-03-04 10:30:00", "from": "+254711000111"}
Response Format: Two-way SMS callbacks expect a simple text response of "GOOD" or "BAD", not JSON.
Handles interactive USSD sessions. This is not a webhook but an interactive endpoint.
routes/ussd.py
@ussd_bp.route("/session", methods=["POST"])def ussd_handler(): # Read the variables sent via POST from our API session_id = request.values.get("sessionId", None) serviceCode = request.values.get("serviceCode", None) phone_number = request.values.get("phoneNumber", None) text = request.values.get("text", "") if text == "": # First request. Start response with CON response = "CON What would you want to check \n" response += "1. My Account \n" response += "2. My phone number" elif text == "1": response = "CON Choose account information you want to view \n" response += "1. Account number" elif text == "2": # Terminal request response = "END Your phone number is " + str(phone_number) elif text == "1*1": accountNumber = "ACC1001" response = "END Your account number is " + accountNumber else: response = "END Invalid choice" return response
Called when a call is answered to get instructions (not a webhook, but a callback).
routes/voice.py
@voice_bp.route("/instruct", methods=["POST"])def voice_instruct(): """ Handle Africa's Talking Voice API call instructions. This endpoint is called when a call is answered to get the next set of instructions. """ session_id = request.values.get("sessionId") caller_number = request.values.get("callerNumber") destination_number = request.values.get("destinationNumber") print(f"📞 Call answered. Session: {session_id}, Caller: {caller_number}") # Return XML instructions response = '<?xml version="1.0" encoding="UTF-8"?>' response += "<Response>" response += "<Say>Welcome to the service. This is a demo voice application. Goodbye.</Say>" response += "</Response>" return Response(response, mimetype="text/plain")
@bp.route("/webhook", methods=["POST"])def webhook_handler(): # Extract all form data payload = {key: request.values.get(key) for key in request.values.keys()} # Log the data print("📩 Webhook Received:") for key, value in payload.items(): print(f" {key}: {value}") # Process if needed # ... your business logic ... # Always acknowledge return Response("OK", status=200)