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{ $t = $event->transaction;
Payment::create([ 'receipt' => $t->transactionId, 'amount' => $t->amount, 'account' => $t->billReferenceNumber, 'payer' => $t->fullName(), 'phone' => $t->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 $t): bool|string { $invoice = Invoice::where('reference', $t->billReferenceNumber)->first();
if (! $invoice) { return 'C2B00012'; // Invalid Account Number }
if ((float) $t->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.