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 an AI Agent That Queries Your Paystack Transactions
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Building an AI Agent That Queries Your Paystack Transactions

Build a read-only Paystack query agent: 1) Define tools: list_transactions (GET /transaction with filters), get_transaction (GET /transaction/:id), search_by_email (GET /transaction?customer=email). 2) Pass tools to Claude API. 3) When Claude calls a tool, execute it server-side using PAYSTACK_SECRET_KEY from env — the key never reaches the LLM. 4) Return tool results to the LLM for synthesis. Keep the agent strictly read-only. Do not include transfer, refund, or charge tools until you have safety rails in place.

Building the Query Agent

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

// Tool definitions — the LLM sees these, not the key
const tools = [
  {
    name: 'list_transactions',
    description: 'List Paystack transactions with optional filters. Returns array of transaction objects.',
    input_schema: {
      type: 'object',
      properties: {
        status: { type: 'string', enum: ['success', 'failed', 'pending'] },
        from: { type: 'string', description: 'ISO date string, e.g. 2026-07-01' },
        to: { type: 'string', description: 'ISO date string' },
        perPage: { type: 'number', description: 'Results per page, max 100' },
      },
    },
  },
  {
    name: 'get_transaction',
    description: 'Get a single Paystack transaction by ID.',
    input_schema: {
      type: 'object',
      properties: { id: { type: 'number' } },
      required: ['id'],
    },
  },
  {
    name: 'search_by_email',
    description: 'Find all transactions from a specific customer email.',
    input_schema: {
      type: 'object',
      properties: { email: { type: 'string' } },
      required: ['email'],
    },
  },
];

// Server-side execution — key never leaves this function
async function executeTool(toolName, toolInput) {
  var headers = { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY };
  if (toolName === 'list_transactions') {
    var params = new URLSearchParams(toolInput).toString();
    var res = await fetch('https://api.paystack.co/transaction?' + params, { headers });
    return (await res.json()).data;
  }
  if (toolName === 'get_transaction') {
    var res = await fetch('https://api.paystack.co/transaction/' + toolInput.id, { headers });
    return (await res.json()).data;
  }
  if (toolName === 'search_by_email') {
    var res = await fetch('https://api.paystack.co/transaction?customer=' + toolInput.email, { headers });
    return (await res.json()).data;
  }
}

// Agent loop
async function runPaystackAgent(userQuestion) {
  var messages = [{ role: 'user', content: userQuestion }];

  while (true) {
    var response = await client.messages.create({
      model: 'claude-opus-4-6',
      max_tokens: 1024,
      tools,
      messages,
    });

    if (response.stop_reason === 'end_turn') {
      return response.content.find(b => b.type === 'text')?.text;
    }

    if (response.stop_reason === 'tool_use') {
      // Execute all tool calls
      var toolResults = [];
      for (var block of response.content) {
        if (block.type === 'tool_use') {
          var result = await executeTool(block.name, block.input);
          toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(result) });
        }
      }
      messages.push({ role: 'assistant', content: response.content });
      messages.push({ role: 'user', content: toolResults });
    }
  }
}

Example usage:

var answer = await runPaystackAgent('How much revenue did we collect last week, broken down by success vs failed?');
console.log(answer);
// → "Last week you processed NGN 1,247,500 in successful transactions (47 payments) and NGN 83,200 in failed attempts (12 payments)."

Learn More

See why you must never give the LLM your secret key for the security reasoning behind this pattern.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Define read-only tools for the LLM — list, get, search. No write tools until you have safety rails.
  • ✓The LLM calls the tool; your server executes it using the secret key from environment variables.
  • ✓The key never appears in the LLM prompt or response — only tool call names and results.
  • ✓Claude function calling returns a structured tool_call object — match on tool name and execute the right handler.
  • ✓Rate limit your agent's tool calls to avoid burning through Paystack API rate limits.

Frequently Asked Questions

What Paystack data can I safely give to the LLM in tool responses?
Transaction IDs, amounts, dates, status, channel (card/mobile money), and reference codes are safe to return to the LLM. Be careful with full card numbers (never return these — Paystack masks them anyway) and customer email addresses (return only what you need for the task). Avoid returning authorization_code in contexts where the LLM output might be logged or displayed.
How do I handle Paystack rate limits in an agent loop?
Paystack enforces rate limits per API key. An agent that runs 10 tool calls per user message can exhaust rate limits quickly. Add a request counter in your executeTool function and throw if it exceeds a per-session cap (e.g., 20 API calls per agent conversation). Also cache list_transactions results within a session — if the LLM calls the same endpoint twice, return the cached result.
Can I use this pattern with GPT-4 or Gemini instead of Claude?
Yes — OpenAI and Google also support function calling with the same server-side execution pattern. The tool definitions differ slightly in schema format (OpenAI uses "function" wrappers, Google uses "functionDeclarations"), but the principle is identical: the LLM sees tool names and results, your server holds the key and executes the calls.

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

Using an LLM to Explain Failed Payments to Customers

"Insufficient Funds" is clear. "Do Not Honor", "Restricted Card", or "Transaction Not Permitted to Cardholder" are not. An LLM translates these into actionable, customer-friendly messages without revealing implementation details.

Read more

Why You Should Never Give an LLM Your Paystack Secret Key

Your Paystack secret key can initialize transactions, trigger transfers, and access all your transaction data. Giving it to an LLM — even once — creates risks that do not go away when you close the chat window.

Read more

Building a Voice Agent That Collects Payment

A customer calls your number. They say what they want to pay for. The voice agent creates a Paystack payment link and texts it to their phone. No human operator needed. Here is how to build it.

Read more

Handle Paystack Webhooks in Android with Kotlin

Paystack webhooks arrive at your server, not your Android app. This guide covers building the backend handler and delivering payment events to an Android Kotlin app via FCM push notifications or polling.

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.