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
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
{
"status": "ok"
}
cURL Example
// 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.
{
"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.