Fortpesa
Blog / Developers / Fortpesa API quickstart: take your first payment...

Fortpesa API quickstart: take your first payment in minutes

Fortpesa API quickstart: take your first payment in minutes
On this page Before you start 1. Take a payment 2. Confirm the payment 3. Errors you might hit Where to go next

This is the shortest path from zero to a live M-Pesa payment in your system: get an API key, create an STK Push, confirm the result. All four code samples below do the same thing — pick the one closest to your stack.

Before you start

You need three things:

  1. An account — create one and sign in.
  2. An approved channel — add a paybill, till or bank channel from the dashboard; its Channel ID is shown in the channels list.
  3. An API key — create it under Settings & API keys. Keys look like pb_live_....

Amounts are whole KES integers everywhere in the API: "amount": 150 is KES 150.

1. Take a payment

Every STK request states its channel explicitly — channel_id is required, so a payment can never land on the wrong account.

cURL

curl -X POST https://fortpesa.co.ke/api/v1/payments/stk \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254712345678",
    "amount": 150,
    "channel_id": 42,
    "reference": "ORDER-1042"
  }'

JavaScript

const res = await fetch('https://fortpesa.co.ke/api/v1/payments/stk', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    phone: '254712345678',
    amount: 150,
    channel_id: 42,
    reference: 'ORDER-1042',
  }),
});
const { data } = await res.json();
console.log(data.id, data.status);

Python

import requests

res = requests.post(
    "https://fortpesa.co.ke/api/v1/payments/stk",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "phone": "254712345678",
        "amount": 150,
        "channel_id": 42,
        "reference": "ORDER-1042",
    },
    timeout=30,
)
tx = res.json()["data"]
print(tx["id"], tx["status"])

PHP

$ch = curl_init('https://fortpesa.co.ke/api/v1/payments/stk');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'phone' => '254712345678',
        'amount' => 150,
        'channel_id' => 42,
        'reference' => 'ORDER-1042',
    ]),
]);
$data = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);

A 201 response means the prompt was dispatched — the customer's phone is ringing with the M-Pesa prompt right now. The response also carries platform_fee, the exact fee for this amount.

{
  "data": {
    "id": "2f0c6a6e-...",
    "status": "pending",
    "amount": 150,
    "platform_fee": 6,
    "reference": "ORDER-1042"
  }
}

2. Confirm the payment

pending means the customer has not finished the prompt yet. Two ways to learn the outcome:

Poll the transaction until it turns terminal:

curl https://fortpesa.co.ke/api/v1/transactions/2f0c6a6e-... \
  -H "Authorization: Bearer YOUR_API_KEY"

Terminal statuses are success and failed. Prompts typically resolve within seconds; treat five minutes as the outer bound.

Or receive a webhook — push instead of poll. Your endpoint receives an HMAC-signed payload:

import crypto from 'crypto';

// timestamp.raw_body is what gets signed
const expected = 'sha256=' + crypto
  .createHmac('sha256', process.env.FORTPESA_WEBHOOK_SECRET)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');

const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

Verify the signature against the raw body before parsing JSON, then mark the order paid. The full retry schedule and payload shapes are in the webhooks documentation.

3. Errors you might hit

Code Meaning Fix
401 Bad or missing key Check the Authorization header
402 Wallet cannot cover the platform fee Top up your wallet
422 Validation failed Missing channel_id, bad phone format or amount ≤ 0
503 No approved channel available Approve a channel in the dashboard

Error bodies follow one envelope everywhere: {"error": {"code": "...", "message": "..."}}.

Where to go next

Create your account, add a channel, and you can be taking real payments before your coffee cools.

← Older What M-Pesa payments cost your business

Start collecting with Fortpesa

Create an account, add a channel and take your first M-Pesa payment — the platform fee schedule is public.