Bonaventure OgetoBy Bonaventure Ogeto|

Building a WhatsApp AI Sales Agent That Takes Payment

Stack: WhatsApp Business Cloud API, Claude with function calling, Paystack Payment Links API. Tools: get_product_catalog(), create_payment_link(product_id, customer_name, customer_email). Flow: 1) WhatsApp webhook delivers customer message to your server. 2) Message + conversation history sent to Claude with tools. 3) Claude identifies product intent, calls get_product_catalog to confirm price. 4) Claude asks customer to confirm before creating link. 5) Claude calls create_payment_link — tool POSTs to Paystack /paymentrequest. 6) Link sent back via WhatsApp API. 7) charge.success webhook fires when customer pays — fulfill and notify.

WhatsApp Sales Agent Architecture

// WhatsApp Business Cloud API webhook handler
app.post('/whatsapp/webhook', async (req, res) => {
  res.sendStatus(200); // acknowledge immediately

  var message = req.body.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
  if (!message || message.type !== 'text') return;

  var from = message.from;      // customer WhatsApp number
  var text = message.text.body;

  // Load conversation history from DB (keyed by phone number)
  var history = await db.whatsappSessions.get(from) || [];
  history.push({ role: 'user', content: text });

  var response = await runSalesAgent(history, from);

  // Send reply via WhatsApp Cloud API
  await sendWhatsAppMessage(from, response.reply);

  // Save updated history
  history.push({ role: 'assistant', content: response.reply });
  await db.whatsappSessions.set(from, history);
});

var salesTools = [
  {
    name: 'get_product_catalog',
    description: 'Get the list of available products with prices.',
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'create_payment_link',
    description: 'Create a Paystack payment link for a confirmed order.',
    input_schema: {
      type: 'object',
      properties: {
        product_id: { type: 'string' },
        customer_name: { type: 'string' },
        customer_email: { type: 'string' },
      },
      required: ['product_id', 'customer_name', 'customer_email'],
    },
  },
];

async function runSalesAgent(history, customerPhone) {
  var response = await anthropic.messages.create({
    model: 'claude-opus-4-6',
    max_tokens: 512,
    tools: salesTools,
    messages: history,
    system: 'You are a sales agent for McTaba. Help customers find the right product and guide them to payment. Always confirm the order before creating a payment link. Collect customer name and email before creating a link.',
  });

  for (var block of response.content) {
    if (block.type === 'tool_use') {
      if (block.name === 'get_product_catalog') {
        var catalog = await db.products.getAll(); // your product DB
        // Feed result back and continue
        return runSalesAgent([
          ...history,
          { role: 'assistant', content: response.content },
          { role: 'user', content: [{ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(catalog) }] },
        ], customerPhone);
      }

      if (block.name === 'create_payment_link') {
        var product = await db.products.getById(block.input.product_id);
        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_kobo,
            currency: 'NGN',
            customer: { email: block.input.customer_email, first_name: block.input.customer_name },
          }),
        });
        var link = (await linkRes.json()).data;
        return { reply: 'Here is your payment link for ' + product.name + ': https://paystack.com/pay/' + link.offline_reference + '

It expires in 24 hours. Reply DONE once you have paid.' };
      }
    }
  }

  return { reply: response.content.find(b => b.type === 'text')?.text };
}

Learn More

See agentic safety rails if you extend this agent to handle payouts or refunds.

Sign up for the McTaba newsletter

Key Takeaways

  • WhatsApp webhook delivers messages to your server; Claude orchestrates the conversation and tool calls.
  • Product catalog lives on your server — Claude never invents prices or products.
  • Collect customer email before creating the Paystack link — required for receipt delivery.
  • Confirm the order before creating the payment link — one confirmation message prevents wrong orders.
  • Charge.success webhook fires on payment — fulfill the order and send confirmation to the WhatsApp number.

Frequently Asked Questions

How do I verify the WhatsApp webhook is from Meta and not spoofed?
Meta signs WhatsApp webhook payloads with the app secret. Verify the x-hub-signature-256 header: compute HMAC-SHA256 of the raw request body using your app secret and compare to the header value. Reject requests that fail verification. This is the same pattern as Paystack HMAC webhook validation.
What happens to conversation history when the session is idle for days?
Store conversation history with a TTL. If the customer has not sent a message in 24 hours, clear the history and start fresh on their next message. WhatsApp Business API sessions expire after 24 hours of customer inactivity anyway — your conversation history should match this boundary.
How do I fulfill the order after payment?
Set up a Paystack webhook handler for charge.success. In the webhook payload, data.metadata.reference or the payment link reference tells you which order was paid. Look up the order, fulfill it (send digital product, create account, dispatch goods), and send a WhatsApp confirmation message to the phone number stored with the order.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon