Customer to Business
C2B covers payments a customer initiates themselves — through the M-Pesa app, USSD, SIM toolkit or a scanned QR code. You register two URLs and Safaricom notifies you when money arrives.
Register your URLs
Section titled “Register your URLs”Daraja::c2b()->registerUrls( confirmationUrl: 'https://your-domain/daraja/c2b/confirmation', validationUrl: 'https://your-domain/daraja/c2b/validation',);Both default to the configured URLs, so with DARAJA_C2B_CONFIRMATION_URL and
DARAJA_C2B_VALIDATION_URL set you can call it bare, or use the command:
php artisan daraja:register-urlsResponse type
Section titled “Response type”responseType decides what M-Pesa does when your validation URL is unreachable:
| Value | Behaviour |
|---|---|
Completed |
Accept the payment anyway (default) |
Cancelled |
Reject the payment |
Spelling and case are exact — Safaricom rejects anything else.
Confirmation
Section titled “Confirmation”Fires after a successful payment:
use Starnerz\LaravelDaraja\Events\C2BPaymentReceived;
public function handle(C2BPaymentReceived $event): void{ $transaction = $event->transaction;
Payment::create([ 'receipt' => $transaction->transactionId, 'amount' => $transaction->amount, 'account' => $transaction->billReferenceNumber, 'payer' => $transaction->fullName(), 'phone' => $transaction->msisdn, // masked: 2547 ***** 126 ]);}The MSISDN is masked in v2. v1 sent a SHA-256 hash instead; neither gives you the full number.
Validation
Section titled “Validation”Validation is optional and disabled by default — email
apisupport@safaricom.co.ke to enable it on your short code. Once on, Safaricom
asks permission before completing each payment and waits roughly 8 seconds.
Because that is synchronous, it uses a decision callback rather than an event:
// In a service provider's boot methoduse Starnerz\LaravelDaraja\Data\Callbacks\C2BTransaction;use Starnerz\LaravelDaraja\Facades\Daraja;
Daraja::validateC2BUsing(function (C2BTransaction $transaction): bool|string { $invoice = Invoice::where('reference', $transaction->billReferenceNumber)->first();
if (! $invoice) { return 'C2B00012'; // Invalid Account Number }
if ((float) $transaction->amount < $invoice->balance) { return 'C2B00013'; // Invalid Amount }
return true;});Return true to accept, false to reject generically, or a specific code:
| Code | Meaning |
|---|---|
C2B00011 |
Invalid MSISDN |
C2B00012 |
Invalid Account Number |
C2B00013 |
Invalid Amount |
C2B00014 |
Invalid KYC Details |
C2B00015 |
Invalid Short code |
C2B00016 |
Other Error |
Keep the callback fast. Queue anything slow — a timeout falls back to your
responseType.
Simulate a payment
Section titled “Simulate a payment”Sandbox only. The package throws if mode is live.
Daraja::c2b()->simulatePayBill('0712345678', 500, 'INV-77');Daraja::c2b()->simulateBuyGoods('0712345678', 500);Missed notifications
Section titled “Missed notifications”If your endpoint was down, Pull Transactions retrieves anything from the last 48 hours.
Laravel Daraja is free and open source, maintained by eazysoft.
An independent open-source project, not affiliated with, endorsed by, or sponsored by Safaricom PLC or Laravel LLC. M-Pesa and Daraja are trademarks of Safaricom PLC; Laravel is a trademark of Laravel LLC. Used here only to say what this package works with.