Pay-In (C2B)

Receive M-Pesa Customer-to-Business pay-ins without your own Daraja shortcode. Customers pay to a SwiftWallet pooled Paybill/Till and enter your unique 5-character account number as the account. Matching pay-ins are credited to your wallet and, if you set and verify a notification URL, forwarded as a signed webhook to your domain. Account numbers and notification URLs are managed from your dashboard at /c2b.php.

/c2b.php
POST https://yourdomain.com/your-c2b-endpoint

Receive a Pay-In Notification (your endpoint)

This is the endpoint YOU host and register as your notification URL. When a customer pays to the pooled Paybill/Till using your account number, SwiftWallet credits your wallet and POSTs this payload to your URL. Verify the X-SwiftWallet-Signature header and respond with HTTP 2xx.

Headers

Header Value
Content-Type application/json
X-SwiftWallet-Signature HMAC-SHA256 of the raw request body, keyed with your forwarding secret
User-Agent SwiftWallet-C2B-Notify/1.0

Request Body

Field Type Required Description
event string Yes Always "c2b.payment.received"
account_code string Yes Your 5-character account number that received the payment
trans_id string Yes M-Pesa receipt number (unique, idempotency key)
trans_type string No Safaricom transaction type (e.g. Pay Bill, Buy Goods)
trans_time string No M-Pesa timestamp YYYYMMDDHHMMSS
amount number Yes Amount paid in KES
business_shortcode string No Pooled Paybill/Till that received the payment
bill_ref_number string No Account number entered by the customer (equals account_code)
msisdn string No Payer phone (254XXXXXXXXX) when Safaricom sends a plain MSISDN; otherwise the SHA-256 MSISDN digest from Safaricom
phone_number string No Full normalized payer phone (254XXXXXXXXX) when available; null when Safaricom only sent a hashed MSISDN
msisdn_hash string No SHA-256 MSISDN digest from Safaricom when plain phone is not available
first_name string No Payer first name
middle_name string No Payer middle name
last_name string No Payer last name
credited boolean Yes Whether the amount was credited to your wallet
payment_id integer Yes SwiftWallet internal pay-in id
timestamp string Yes ISO 8601 time the notification was sent

Example Response

JSON
{
    "status": "ok"
}

cURL Example

bash
// Verify the signature in your endpoint (PHP example)
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SWIFTWALLET_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $raw, $YOUR_FORWARDING_SECRET);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }
$event = json_decode($raw, true);
// ... process $event['trans_id'] (idempotent) ...
http_response_code(200);
echo 'ok';

Callback Payload

Example payload SwiftWallet POSTs to your verified notification URL when a pay-in is matched to your account number.

POST to your callback_url
{
    "event": "c2b.payment.received",
    "account_code": "AB3KP",
    "trans_id": "SFE1A2B3C4",
    "trans_type": "Pay Bill",
    "trans_time": "20260603120500",
    "amount": 250,
    "business_shortcode": "174379",
    "bill_ref_number": "AB3KP",
    "msisdn": "254712345678",
    "phone_number": "254712345678",
    "msisdn_hash": null,
    "first_name": "JOHN",
    "middle_name": "",
    "last_name": "DOE",
    "credited": true,
    "payment_id": 1024,
    "timestamp": "2026-06-03T12:05:06+03:00"
}

Recompute hash_hmac("sha256", rawBody, CALLBACK_FORWARDING_SECRET from your SwiftWallet deployment) and compare in constant time with the X-SwiftWallet-Signature header (hex digest, optional sha256= prefix). Treat trans_id as the idempotency key. Respond with HTTP 2xx to acknowledge. Production M-Pesa C2B may send MSISDN as a SHA-256 hash only; phone_number is set when a plain MSISDN is available.

Notes

  • How it works: 1) Generate a unique 5-character account number on the Pay-In (C2B) page. 2) Share the displayed Paybill/Till and your account number with your customer. 3) The customer pays; SwiftWallet credits your wallet for the matched account number and records it as an incoming transaction. 4) If you set and verify a notification URL, SwiftWallet forwards a signed webhook. 5) A notification fee is deducted from your service wallet for each notification request we send to your URL. The fee uses the same tiered Service Fee schedule as STK collections (it depends on the pay-in amount) — incoming deposits are credited free of charge.
  • Notifications are only forwarded after your domain is verified.
  • Notification URLs must use HTTPS on a publicly reachable production domain.
  • Notification URLs must not contain banned keywords: mpesa, m-pesa, safaricom, exe, cmd, sql, query.
  • Notification URLs must not use public URL testers (ngrok, mockbin, requestbin) or localhost / 127.0.0.1.
  • Verify domain ownership by serving /.well-known/swiftwallet-c2b-<token>.txt containing the token shown on your account.