McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Building a Voice Agent That Collects Payment
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Building a Voice Agent That Collects Payment

Stack: Twilio (phone call reception), Deepgram or Whisper (speech-to-text), Claude with function calling (intent detection + orchestration), Paystack payment links (POST /paymentrequest), Twilio SMS (link delivery). Flow: 1) Customer calls Twilio number. 2) Audio streamed to STT. 3) Transcript sent to Claude with a create_payment_link tool. 4) Claude identifies product and amount from conversation, calls tool. 5) Tool creates Paystack payment link. 6) Claude calls send_sms tool — link texted to caller's number. 7) Customer pays on phone. Critical: Claude picks amount from a product catalog, not from what the caller says — callers must not be able to specify arbitrary amounts.

Voice Agent Flow and Code

// Express endpoint for Twilio voice webhook
app.post('/voice', async (req, res) => {
  // Twilio sends speech input via SpeechResult when using 
  var speechResult = req.body.SpeechResult;
  var callerPhone = req.body.From;

  if (!speechResult) {
    // Initial greeting — prompt caller
    res.type('text/xml').send(`
      <Response>
        <Gather input="speech" action="/voice" timeout="5" speechTimeout="auto">
          <Say>Welcome. What would you like to pay for today?</Say>
        </Gather>
      </Response>
    `);
    return;
  }

  // Send transcript to Claude with tools
  var result = await runVoiceAgent(speechResult, callerPhone);

  res.type('text/xml').send(`
    <Response>
      <Say>${result.voiceResponse}</Say>
    </Response>
  `);
});

// Product catalog — Claude maps intent here, does not accept caller amounts
var PRODUCTS = {
  'consulting': { name: '1-hour consulting session', amount: 15000_00 }, // NGN 15,000 in kobo
  'basic plan': { name: 'Basic monthly plan', amount: 5000_00 },
  'premium plan': { name: 'Premium monthly plan', amount: 25000_00 },
};

var voiceTools = [
  {
    name: 'create_payment_link',
    description: 'Create a Paystack payment link for a product and send it to the customer via SMS.',
    input_schema: {
      type: 'object',
      properties: {
        product_key: { type: 'string', enum: Object.keys(PRODUCTS) },
        customer_phone: { type: 'string' },
      },
      required: ['product_key', 'customer_phone'],
    },
  },
];

async function runVoiceAgent(transcript, callerPhone) {
  var messages = [
    {
      role: 'user',
      content: 'Customer said: "' + transcript + '". Customer phone: ' + callerPhone +
        '. Available products: ' + JSON.stringify(Object.keys(PRODUCTS)) +
        '. If you can identify the product, create a payment link and confirm what you're creating before sending.',
    },
  ];

  var response = await anthropic.messages.create({
    model: 'claude-opus-4-6',
    max_tokens: 512,
    tools: voiceTools,
    messages,
    system: 'You are a voice payment assistant. Only create payment links for products in the catalog. Never accept amounts from the caller.',
  });

  // Handle tool call
  for (var block of response.content) {
    if (block.type === 'tool_use' && block.name === 'create_payment_link') {
      var product = PRODUCTS[block.input.product_key];
      // Create Paystack link
      var linkRes = await fetch('https://api.paystack.co/paymentrequest', {
        method: 'POST',
        headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
        body: JSON.stringify({ description: product.name, amount: product.amount, currency: 'NGN' }),
      });
      var link = (await linkRes.json()).data?.offline_reference;
      // Send SMS via Twilio
      await twilioClient.messages.create({ to: callerPhone, from: process.env.TWILIO_NUMBER, body: 'Your payment link: https://paystack.com/pay/' + link });
      return { voiceResponse: 'I have created a payment link for ' + product.name + ' and sent it to your phone.' };
    }
  }

  return { voiceResponse: response.content.find(b => b.type === 'text')?.text || 'I could not identify the product. Please try again.' };
}

Learn More

See building a WhatsApp AI sales agent for a similar pattern over chat instead of voice.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Use Twilio for call reception, STT (Deepgram/Whisper) for transcription, Claude for intent + orchestration.
  • ✓Claude identifies what the customer wants to pay for and maps it to a product catalog — it does not accept caller-dictated amounts.
  • ✓The payment link is texted to the caller's Twilio number after creation, not read aloud.
  • ✓Confirm the product and amount with the caller before creating the payment link.
  • ✓Log every voice session: caller number, transcript, product identified, link created, payment status.

Frequently Asked Questions

What if the caller says a product that is not in the catalog?
The LLM should not guess or map ambiguous input to the nearest product — it should ask for clarification: "I didn't recognize that product. We have consulting, basic plan, and premium plan. Which did you mean?" Your system prompt should make this explicit. Never let the LLM create a payment link for a product it cannot confidently identify from the catalog.
How do I handle poor audio quality or heavy accents?
Use a STT service with strong African English support. Deepgram Nova-2 and Whisper large-v3 both handle Kenyan and Nigerian English well. Add a confirmation step before creating the link — the agent reads back what it understood before executing. Give the caller a chance to correct misunderstandings before money changes hands.
Can Paystack payment links be created via API?
Yes — POST /paymentrequest creates a payment link programmatically. The response includes offline_reference which gives you the short link at paystack.com/pay/{reference}. You can also set customer email, amount, description, and due date. For one-time vs reusable links, set allow_multiple_use accordingly. See the Paystack API docs for full parameters.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack AI Agents and the MCP Server: The Complete Guide

A comprehensive guide to building AI agents that interact with Paystack through the Model Context Protocol (MCP) server. Covers connecting the MCP server to Claude Code, querying transactions with natural language, building payment support chatbots, AI-assisted reconciliation, agentic payouts with safety rails, secret key security, human-in-the-loop design, prompt injection risks, and audit trails for AI-initiated financial actions.

Read more

RAG Over Your Payment Documentation and Transaction History

RAG lets an LLM answer questions grounded in your actual Paystack docs and transaction history instead of its training data. "What does this error code mean?" retrieves from your embedded docs. "What happened to transaction X?" retrieves from your transaction store.

Read more

Agentic Payouts: Safety Rails You Must Build First

An AI agent that can trigger Paystack transfers needs guardrails that a human operator does not. Build these safety rails before you give any agent access to POST /transfer — or you will build them after the first incident.

Read more

Building a WhatsApp AI Sales Agent That Takes Payment

A customer messages your WhatsApp number: "I want the premium plan." The AI agent replies with the price, confirms, and sends a Paystack payment link — all without a human operator. Here is how to build it.

Read more

Build Paystack Subscriptions in Firebase Cloud Functions

Add recurring billing to a Firebase project using Paystack subscriptions and Cloud Functions. This guide covers subscription checkout with a plan code, webhook handling for subscription.create and invoice.payment_failed, and checking subscription status from Firestore.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.