Bonaventure OgetoBy Bonaventure Ogeto|

Airtel Money API Integration in Kenya: Direct API and Paystack Guide

To integrate Airtel Money in Kenya, you have two options. Option one: use the official Airtel Africa API directly. Register at developers.airtel.africa, get your client_id and client_secret, generate an OAuth token, then call the Collection API endpoint to trigger a payment prompt on the customer's phone. Option two: use Paystack as an aggregator. Initialize a charge with the mobile_money object, set the provider to "atl" and currency to "KES", and Paystack handles the rest. The direct API gives you more control. Paystack is faster to set up and handles multiple payment methods from one integration.

Why Airtel Money Matters for Kenyan Developers

M-Pesa dominates Kenya's mobile money market, but Airtel Money is the second-largest player with millions of active subscribers. If your app only accepts M-Pesa, you are telling every Airtel Money user to go find an M-Pesa agent first. That is friction. And friction kills conversions.

Airtel Money has been growing steadily in Kenya, especially in price-sensitive segments where Airtel's cheaper data and call rates attract subscribers. Many users keep both an M-Pesa and an Airtel Money account. Some use Airtel Money as their primary wallet. Ignoring them means leaving money on the table.

The good news: integrating Airtel Money is straightforward. The Airtel Africa API follows modern REST conventions with OAuth 2.0 authentication and JSON payloads. If you have integrated M-Pesa's Daraja API before, the Airtel Money API will feel familiar. And if you use Paystack, you can add Airtel Money support with about ten lines of code.

This tutorial covers both approaches:

  • Method 1: Direct Airtel Africa API for full control over the payment flow, custom callback handling, and no middleman fees beyond Airtel's own charges.
  • Method 2: Paystack for a faster setup, a unified dashboard for all payment methods, and built-in tools for reconciliation and dispute management.

All code examples use Node.js with the built-in fetch API (Node 18+). The concepts apply to any language that can make HTTP requests.

Method 1: Direct Integration with the Airtel Africa API

The Airtel Africa API is the official developer interface for Airtel Money across 14 African countries, including Kenya. It supports collections (receiving payments), disbursements (sending money), and transaction status enquiries. The API is managed through the Airtel developer portal at developers.airtel.africa.

Here is the full flow you will implement:

  1. Register on the developer portal and create an application
  2. Get your client_id and client_secret
  3. Generate an OAuth access token
  4. Call the Collection API to request a payment
  5. Handle the callback when the customer completes (or rejects) the payment
  6. Optionally query the transaction status

Let us walk through each step.

Step 1: Register on the Airtel Developer Portal

Go to developers.airtel.africa and sign up for a developer account. You will need a valid email address and basic business information.

  1. Click "Sign Up" or "Get Started" on the portal
  2. Fill in your details: name, email, organization name, and country (select Kenya)
  3. Verify your email
  4. Log in and navigate to the dashboard

Once logged in, create a new application:

  1. Go to the "My Applications" or "Apps" section
  2. Click "Create New Application" or "Add App"
  3. Give your app a descriptive name (for example, "MyStore Payments")
  4. Select the APIs you need. For accepting payments, select "Collection" at minimum
  5. Submit the application

After creating the application, the portal gives you two credentials:

  • Client ID: Your application identifier
  • Client Secret: Your application's secret key. Keep this confidential.

Store these in a .env file. Never commit them to version control:

AIRTEL_CLIENT_ID=your_client_id_here
AIRTEL_CLIENT_SECRET=your_client_secret_here
AIRTEL_ENV=sandbox

For Kenya, you also need to note two values you will use in every API request header:

  • X-Country: KE
  • X-Currency: KES

Airtel requires KYC (Know Your Customer) verification before you can access the production environment. For sandbox testing, your developer account is enough. The KYC process involves submitting business registration documents and can take a few days. Start this early if you know you will need production access soon.

Step 2: Generate an OAuth Access Token

Every API call to Airtel Money requires a Bearer token in the Authorization header. You get this token by sending your client credentials to the OAuth endpoint.

The base URLs are:

  • Sandbox: https://openapiuat.airtel.africa
  • Production: https://openapi.airtel.africa

Here is how to generate a token in Node.js:

async function getAirtelToken() {
  const baseUrl = process.env.AIRTEL_ENV === 'production'
    ? 'https://openapi.airtel.africa'
    : 'https://openapiuat.airtel.africa';

  const response = await fetch(`${baseUrl}/auth/oauth2/token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      client_id: process.env.AIRTEL_CLIENT_ID,
      client_secret: process.env.AIRTEL_CLIENT_SECRET,
      grant_type: 'client_credentials',
    }),
  });

  const data = await response.json();
  console.log('Access Token:', data.access_token);
  console.log('Expires in:', data.expires_in, 'seconds');

  return data.access_token;
}

A successful response looks like this:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "token_type": "bearer"
}

The token expires after 3600 seconds (one hour). In a production application, cache the token and refresh it before it expires. A simple approach: store the token and its expiry timestamp, then check before each API call whether you need a fresh token.

If you get a 401 or 400 error, check these common issues:

  • Extra whitespace in your client_id or client_secret (copy carefully from the portal)
  • Wrong base URL (sandbox vs production)
  • Missing or incorrect Content-Type header
  • The grant_type must be exactly client_credentials

Step 3: Accept Payments with the Collection API

The Collection API is what you use to request money from a customer. When you call it, Airtel sends a USSD push to the customer's phone asking them to enter their Airtel Money PIN to confirm the payment. This is similar to how M-Pesa's STK Push works.

The endpoint is POST /merchant/v2/payments/ on the base URL.

Here is a complete payment request in Node.js:

async function requestPayment(accessToken, phoneNumber, amount, reference) {
  const baseUrl = process.env.AIRTEL_ENV === 'production'
    ? 'https://openapi.airtel.africa'
    : 'https://openapiuat.airtel.africa';

  const response = await fetch(`${baseUrl}/merchant/v2/payments/`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken}`,
      'X-Country': 'KE',
      'X-Currency': 'KES',
    },
    body: JSON.stringify({
      reference: reference,
      subscriber: {
        country: 'KE',
        currency: 'KES',
        msisdn: phoneNumber,
      },
      transaction: {
        amount: amount,
        country: 'KE',
        currency: 'KES',
        id: reference,
      },
    }),
  });

  const data = await response.json();
  console.log('Payment Response:', data);
  return data;
}

// Usage
const token = await getAirtelToken();
const result = await requestPayment(
  token,
  '7XXXXXXXX',  // Customer phone number without country code
  500,           // Amount in KES
  'ORDER-12345'  // Your unique reference
);

Important details about the request:

  • msisdn: The customer's phone number without the country code. For a Kenyan number like +254 712 345 678, use 712345678.
  • reference: A unique string you generate for each transaction. Use this to track the payment in your system.
  • transaction.id: Can be the same as your reference. This is the identifier Airtel uses internally.
  • X-Country and X-Currency headers: These are required on every API call, not just authentication. For Kenya, always use KE and KES.

A successful response means Airtel received your request and will send the USSD push to the customer:

{
  "data": {
    "transaction": {
      "id": "ABC123456789",
      "status": "SUCCESS"
    }
  },
  "status": {
    "code": "200",
    "message": "SUCCESS",
    "result_code": "ESB000010",
    "success": true
  }
}

Like M-Pesa's STK Push, this response only confirms that Airtel accepted your request. It does not mean the customer has paid. The customer still needs to enter their PIN on their phone. The actual payment result comes through the callback.

Step 4: Handle Payment Callbacks

After the customer confirms (or rejects) the payment on their phone, Airtel sends a POST request to the callback URL you configured in your application on the developer portal. This is where you find out whether the payment actually went through.

Set up a callback endpoint in your Express app:

const express = require('express');
const app = express();

app.use(express.json());

app.post('/airtel/callback', (req, res) => {
  const payload = req.body;

  console.log('Airtel Callback Received:', JSON.stringify(payload, null, 2));

  const transactionId = payload.transaction?.id;
  const status = payload.transaction?.status_code;

  if (status === 'TS') {
    // Payment successful
    console.log('Payment confirmed for transaction:', transactionId);
    // Update your database, fulfill the order, send confirmation
  } else if (status === 'TF') {
    // Payment failed
    console.log('Payment failed for transaction:', transactionId);
    // Notify the customer, allow retry
  } else {
    // Other status (TA = ambiguous, etc.)
    console.log('Payment status:', status, 'for transaction:', transactionId);
  }

  // Always respond with 200 to acknowledge receipt
  res.status(200).json({ status: 'received' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

The key status codes in the callback are:

  • TS: Transaction Successful. The customer confirmed and the money moved.
  • TF: Transaction Failed. The customer rejected the payment, had insufficient balance, or the request timed out.
  • TA: Transaction Ambiguous. The status is uncertain. Query the Transaction Status API to get a definitive answer.

During development, your localhost is not reachable from Airtel's servers. Use a tunneling tool like ngrok to expose your local server:

npx ngrok http 3000

This gives you a public URL like https://abc123.ngrok.io. Use https://abc123.ngrok.io/airtel/callback as your callback URL in the Airtel developer portal. If you have worked with M-Pesa callbacks before, this is the exact same concept. If your callbacks are not arriving, our guide on callback URLs not firing covers the common causes.

Step 5: Check Transaction Status

Sometimes callbacks do not arrive (network issues, server downtime, misconfigured URLs). The Transaction Status API lets you check the status of any payment by its transaction ID.

async function checkTransactionStatus(accessToken, transactionId) {
  const baseUrl = process.env.AIRTEL_ENV === 'production'
    ? 'https://openapi.airtel.africa'
    : 'https://openapiuat.airtel.africa';

  const response = await fetch(
    `${baseUrl}/standard/v1/payments/${transactionId}`,
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-Country': 'KE',
        'X-Currency': 'KES',
      },
    }
  );

  const data = await response.json();
  console.log('Transaction Status:', data);
  return data;
}

The response includes the transaction status and amount:

{
  "data": {
    "transaction": {
      "airtel_money_id": "CI260720.1234.X12345",
      "id": "ORDER-12345",
      "status": "TS",
      "message": "Transaction Successful"
    }
  },
  "status": {
    "code": "200",
    "message": "SUCCESS",
    "success": true
  }
}

Use this endpoint as a fallback, not as your primary notification method. Polling the status API repeatedly is inefficient and can get your application rate-limited. The recommended pattern is: rely on callbacks as your primary notification, and use the status API only when a callback does not arrive within a reasonable timeframe (for example, after 2 to 3 minutes).

Method 2: Airtel Money via Paystack

If you already use Paystack for card payments or M-Pesa in Kenya, adding Airtel Money takes minimal effort. Paystack supports Airtel Money through their Charge API with the provider code atl. This means one integration, one dashboard, and one webhook endpoint for all your payment methods.

Paystack's Airtel Money support is available to businesses registered in Kenya (and Ghana). You need a Paystack account with KES as a supported currency.

There are two ways to accept Airtel Money through Paystack:

  • Paystack Checkout (recommended for most apps): Initialize a transaction with mobile_money as a channel, and Paystack's hosted checkout page handles the rest.
  • Charge API (for custom flows): Directly charge Airtel Money from your backend with full control over the UI.

Let us cover both.

Paystack Checkout with Mobile Money

The simplest approach is to initialize a transaction and let Paystack's checkout page show the Airtel Money option to your customer. You specify which payment channels to display.

async function initializePaystackTransaction(email, amount) {
  const response = await fetch(
    'https://api.paystack.co/transaction/initialize',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: email,
        amount: amount * 100,  // Paystack uses lowest currency unit (cents)
        currency: 'KES',
        channels: ['mobile_money'],  // Only show mobile money options
        callback_url: 'https://yoursite.com/payment/callback',
        metadata: {
          order_id: 'ORDER-12345',
          custom_fields: [
            {
              display_name: 'Order Number',
              variable_name: 'order_number',
              value: 'ORDER-12345',
            },
          ],
        },
      }),
    }
  );

  const data = await response.json();
  console.log('Checkout URL:', data.data.authorization_url);
  console.log('Reference:', data.data.reference);

  return data.data;
}

// Usage: KES 500 payment
const checkout = await initializePaystackTransaction(
  'customer@example.com',
  500
);

The response gives you a checkout URL:

{
  "status": true,
  "message": "Authorization URL created",
  "data": {
    "authorization_url": "https://checkout.paystack.com/nkdks46nymizns7",
    "access_code": "nkdks46nymizns7",
    "reference": "nms6uvr1pl"
  }
}

Redirect your customer to the authorization_url. Paystack's checkout page will display available mobile money providers for Kenya (M-Pesa and Airtel Money). The customer selects Airtel Money, enters their phone number, and confirms the payment on their phone.

If you want customers to see all available payment methods (cards, M-Pesa, and Airtel Money), remove the channels parameter entirely. Paystack will show everything that is enabled for your account.

Note the amount field: Paystack expects the amount in the smallest currency unit. For KES, that means cents. So KES 500 becomes 50000. This is a common source of bugs. If your customers are being charged 100x the expected amount, check your amount conversion.

Paystack Charge API for Airtel Money

For full control over the payment flow (your own UI, no redirects to Paystack's checkout page), use the Charge API directly. This lets you collect the phone number in your own form and charge Airtel Money from your backend.

async function chargeAirtelMoney(email, amount, phoneNumber) {
  const response = await fetch('https://api.paystack.co/charge', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: email,
      amount: amount * 100,  // Convert to cents
      currency: 'KES',
      mobile_money: {
        phone: phoneNumber,  // e.g., '0738123456'
        provider: 'atl',     // 'atl' = Airtel Money
      },
    }),
  });

  const data = await response.json();
  console.log('Charge Response:', data);
  return data;
}

// Usage
const charge = await chargeAirtelMoney(
  'customer@example.com',
  500,
  '0738123456'
);

The response tells you the charge is pending customer authorization:

{
  "status": true,
  "message": "Charge attempted",
  "data": {
    "reference": "abc123def456",
    "status": "pay_offline",
    "display_text": "Please complete the payment on your mobile phone"
  }
}

The pay_offline status means the customer has received a prompt on their phone and needs to enter their Airtel Money PIN to confirm. They have 180 seconds (3 minutes) to complete the authorization.

Key details about the Charge API:

  • provider: Use atl for Airtel Money. Other options include mpesa for M-Pesa in Kenya.
  • phone: The customer's Airtel phone number. Use the local format (for example, 0738123456).
  • email: Required by Paystack even for mobile money charges. Use the customer's email or a placeholder if your business model does not collect emails.
  • Recurring charges are not supported: Each Airtel Money payment requires a new charge. You cannot save the authorization for future charges.

Paystack Webhooks and Payment Verification

After the customer completes (or fails) the payment, Paystack notifies your server through a webhook. You must set up a webhook URL in your Paystack dashboard under Settings > API Keys & Webhooks.

Here is a webhook handler in Express:

const crypto = require('crypto');

app.post('/paystack/webhook', (req, res) => {
  // Verify the webhook signature
  const hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== req.headers['x-paystack-signature']) {
    console.log('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }

  const event = req.body;

  if (event.event === 'charge.success') {
    const { reference, amount, currency, channel } = event.data;

    console.log('Payment successful:', {
      reference,
      amount: amount / 100,  // Convert back from cents
      currency,
      channel,  // Will be 'mobile_money' for Airtel Money
    });

    // Verify the amount matches what you expected
    // Update your database, fulfill the order
  }

  // Always respond with 200 immediately
  res.status(200).send('OK');
});

Always verify the webhook signature using the x-paystack-signature header. This proves the webhook came from Paystack and was not forged by an attacker. The signature is an HMAC SHA512 hash of the raw request body, computed with your Paystack secret key.

As an additional safety measure, verify the transaction through the API after receiving a webhook:

async function verifyPaystackTransaction(reference) {
  const response = await fetch(
    `https://api.paystack.co/transaction/verify/${reference}`,
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      },
    }
  );

  const data = await response.json();

  if (data.data.status === 'success') {
    // Payment confirmed
    const amountPaid = data.data.amount / 100;
    console.log('Verified payment:', amountPaid, data.data.currency);
    return true;
  }

  return false;
}

If the customer does not complete the payment within 180 seconds, the charge fails. You will not receive a webhook for failed mobile money charges, so use the Verify Transaction API to check the status if no webhook arrives after 3 minutes.

Direct Airtel API vs Paystack: Which Should You Choose?

Both approaches work. The right choice depends on your situation:

Choose the direct Airtel Africa API if:

  • You want the lowest transaction costs (no aggregator markup on top of Airtel's fees)
  • You need fine-grained control over the payment flow
  • You only need Airtel Money (no cards, no M-Pesa through the same integration)
  • You are building a high-volume service where the cost savings from direct integration add up

Choose Paystack if:

  • You already use Paystack for other payment methods (cards, M-Pesa, bank transfers)
  • You want a single dashboard for all transactions across all payment channels
  • You need to get up and running fast without managing separate API credentials for each provider
  • You want built-in tools for refunds, disputes, settlements, and reporting
  • You plan to expand to other African markets where Paystack operates

For most Kenyan startups and small businesses, Paystack is the pragmatic choice. The transaction fees (1.5% for mobile money in Kenya) are reasonable, and the time saved on integration, reconciliation, and compliance is significant. If you are processing high volumes and every basis point matters, the direct API makes more financial sense.

Nothing stops you from using both. Some businesses use the direct Airtel API for high-volume automated payments and Paystack for their customer-facing checkout. The important thing is understanding how both work so you can make an informed decision.

Production Checklist: Before You Go Live

Whether you use the direct API or Paystack, run through this checklist before accepting real payments:

For the direct Airtel Africa API:

  1. Complete KYC verification on the Airtel developer portal. This requires business registration documents.
  2. Switch your base URL from openapiuat.airtel.africa to openapi.airtel.africa.
  3. Replace sandbox credentials with production client_id and client_secret.
  4. Set up your callback URL on a publicly accessible HTTPS endpoint (not ngrok).
  5. Implement proper error handling for network failures, timeouts, and ambiguous transaction states.
  6. Build a reconciliation process that uses the Transaction Status API to verify any callbacks you might have missed.
  7. Store every transaction ID, reference, and callback payload in your database for audit trails.

For Paystack:

  1. Switch from test keys (sk_test_*) to live keys (sk_live_*) in your environment variables.
  2. Register your production webhook URL in the Paystack dashboard.
  3. Verify webhook signatures in production (not just in development).
  4. Always verify the transaction amount server-side before fulfilling orders. A mismatch between the amount you expected and the amount actually paid could indicate a tampered request.
  5. Handle the case where webhooks arrive before or after the customer is redirected back to your site. Both should work correctly.

For both approaches:

  • Never hardcode credentials. Use environment variables.
  • Use HTTPS for all callback and webhook URLs.
  • Log everything. When a payment goes wrong at 2 AM, your logs are the only thing that can tell you what happened.
  • Test the full flow with a real Airtel Money account before launching. Sandbox behavior does not always match production exactly.

If you want to build production-ready payment integrations with proper error handling, retry logic, and reconciliation patterns, our Full-Stack Software and AI Engineering program (KES 120,000) covers payment integrations for the Kenyan market. You ship real projects that accept real money before you graduate. For a focused deep dive on mobile money, the M-Pesa Integration for Developers course (KES 9,999) covers both M-Pesa and Airtel Money patterns.

Complete Working Example: Express.js Payment Server

Here is a minimal but complete Express.js server that accepts Airtel Money payments through both methods. Use this as a starting point and build on it.

require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// ---- Airtel Money Direct API ----

let airtelToken = null;
let airtelTokenExpiry = 0;

async function getAirtelToken() {
  if (airtelToken && Date.now() < airtelTokenExpiry) {
    return airtelToken;
  }

  const baseUrl = process.env.AIRTEL_ENV === 'production'
    ? 'https://openapi.airtel.africa'
    : 'https://openapiuat.airtel.africa';

  const response = await fetch(`${baseUrl}/auth/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: process.env.AIRTEL_CLIENT_ID,
      client_secret: process.env.AIRTEL_CLIENT_SECRET,
      grant_type: 'client_credentials',
    }),
  });

  const data = await response.json();
  airtelToken = data.access_token;
  airtelTokenExpiry = Date.now() + (data.expires_in - 60) * 1000;

  return airtelToken;
}

app.post('/pay/airtel-direct', async (req, res) => {
  const { phone, amount, orderId } = req.body;

  const token = await getAirtelToken();
  const baseUrl = process.env.AIRTEL_ENV === 'production'
    ? 'https://openapi.airtel.africa'
    : 'https://openapiuat.airtel.africa';

  const response = await fetch(`${baseUrl}/merchant/v2/payments/`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
      'X-Country': 'KE',
      'X-Currency': 'KES',
    },
    body: JSON.stringify({
      reference: orderId,
      subscriber: { country: 'KE', currency: 'KES', msisdn: phone },
      transaction: { amount, country: 'KE', currency: 'KES', id: orderId },
    }),
  });

  const data = await response.json();
  res.json(data);
});

app.post('/airtel/callback', (req, res) => {
  const { transaction } = req.body;
  if (transaction?.status_code === 'TS') {
    // Payment successful - update your database
    console.log('Airtel payment confirmed:', transaction.id);
  }
  res.status(200).json({ status: 'received' });
});

// ---- Paystack Airtel Money ----

app.post('/pay/paystack-airtel', async (req, res) => {
  const { email, phone, amount } = req.body;

  const response = await fetch('https://api.paystack.co/charge', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      amount: amount * 100,
      currency: 'KES',
      mobile_money: { phone, provider: 'atl' },
    }),
  });

  const data = await response.json();
  res.json(data);
});

app.post('/paystack/webhook', (req, res) => {
  const hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== req.headers['x-paystack-signature']) {
    return res.status(401).send('Unauthorized');
  }

  if (req.body.event === 'charge.success') {
    const { reference, amount } = req.body.data;
    console.log('Paystack payment confirmed:', reference, amount / 100, 'KES');
    // Update your database
  }

  res.status(200).send('OK');
});

app.listen(3000, () => console.log('Payment server running on port 3000'));

This example handles token caching, both payment methods, callback verification, and webhook signature validation. In a real application, you would add input validation, database operations, proper error responses, and logging. But this gives you a working foundation to build on.

Next Steps

You now know how to accept Airtel Money payments in Kenya through both the direct API and Paystack. Here is where to go from here:

  • Add M-Pesa alongside Airtel Money. If you used the Paystack approach, just add mpesa as another provider option. For direct integration, check our Daraja API tutorial for beginners.
  • Build disbursements. The Airtel Africa API also supports sending money from your business to customers (refunds, payouts, commissions). The flow is similar to collections but uses the /standard/v2/disbursements/ endpoint.
  • Handle edge cases. Timeouts, duplicate payments, partial failures. Our guide on handling failed and duplicate payments covers patterns that apply to both M-Pesa and Airtel Money.
  • Learn to test payment integrations properly. Our testing guide covers strategies that work for any mobile money API.

Mobile money integration is one of the most marketable skills for a developer in Kenya. Every business that serves Kenyan customers needs this. If you want structured training with real projects, check out the courses at McTaba Academy. The M-Pesa Integration for Developers course (KES 9,999) is the fastest way to build production-ready payment skills for the Kenyan market.

Key Takeaways

  • The Airtel Africa API uses OAuth 2.0 client credentials for authentication. You generate a token by sending your client_id and client_secret to the auth endpoint, and that token is valid for one hour.
  • The Collection API triggers a USSD push on the customer's phone asking them to confirm the payment with their Airtel Money PIN. The flow is asynchronous: you get an acknowledgement first, then the actual result through a callback.
  • Paystack supports Airtel Money in Kenya through the Charge API with provider code "atl". This is the fastest way to accept Airtel Money if you already use Paystack for cards or M-Pesa.
  • Always test in the sandbox first. The Airtel staging environment uses openapiuat.airtel.africa and does not move real money. Paystack has a test mode with test keys.
  • For production apps in Kenya, supporting both M-Pesa and Airtel Money covers over 95% of the mobile money market. Paystack lets you do both from one integration.

Frequently Asked Questions

Is the Airtel Money API free to use?
The API itself is free. Airtel does not charge you for making API calls. You pay standard Airtel Money transaction fees on successful payments, just like any other Airtel Money merchant. In the sandbox environment, everything is free because no real money moves.
Do I need a business to integrate Airtel Money in Kenya?
For sandbox testing, a developer account on developers.airtel.africa is enough. For production access, Airtel requires KYC verification which involves submitting business registration documents. With Paystack, you need a registered Paystack business account, which also requires business documentation.
What is the Airtel Money provider code in Paystack?
The provider code for Airtel Money in Paystack is "atl". You pass this in the mobile_money.provider field when using the Charge API. For M-Pesa, the code is "mpesa". These codes are case-sensitive.
Can I accept both M-Pesa and Airtel Money in one app?
Yes. With Paystack, you can accept both by using the Checkout flow with channels set to ["mobile_money"]. Paystack will show both options to the customer. With the Charge API, you choose the provider per transaction. For direct integration, you would integrate both the Daraja API (for M-Pesa) and the Airtel Africa API (for Airtel Money) separately in your backend.
What happens if the customer does not complete the Airtel Money payment?
With the direct Airtel API, the USSD prompt times out after the customer fails to respond, and you receive a callback with status code "TF" (Transaction Failed). With Paystack, the charge times out after 180 seconds. You can check the final status using the Verify Transaction API at api.paystack.co/transaction/verify/{reference}.
What are the Airtel Money API sandbox and production URLs?
The sandbox base URL is https://openapiuat.airtel.africa and the production base URL is https://openapi.airtel.africa. All API paths (like /auth/oauth2/token and /merchant/v2/payments/) are the same for both environments. Only the base URL changes.
How much does Paystack charge for Airtel Money transactions in Kenya?
Paystack charges 1.5% per mobile money transaction in Kenya. This applies to both M-Pesa and Airtel Money charges. There are no setup fees or monthly fees. You only pay when you successfully process a payment.
Can I use Airtel Money for recurring or subscription payments?
Not directly. Both the direct Airtel API and Paystack require the customer to authorize each payment individually by entering their PIN on their phone. You cannot save an authorization for future charges. For subscription models, you need to initiate a new charge each billing cycle and have the customer confirm each time.
Which programming languages work with the Airtel Money API?
Any language that can make HTTP requests. The Airtel Africa API is a standard REST API with JSON payloads. Node.js, Python, PHP, Java, Go, Ruby, C#, and any other language with HTTP client capabilities will work. This tutorial uses Node.js because it is the most popular choice among Kenyan developers.
How do I handle Airtel Money payment failures in production?
Log every transaction attempt and callback. For the direct API, check the callback status_code: "TS" for success, "TF" for failure, "TA" for ambiguous. For ambiguous states, query the Transaction Status API. With Paystack, verify every payment through the Verify Transaction API after receiving a webhook. Always give customers a way to retry failed payments.

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