SADAD Payment Integration: Complete Technical Guide

SADAD = Saudi payment system. Integration requires Saudi server + agreement + security certificates. We explain the complete steps.
SADAD — Background and Importance
SADAD is Saudi Arabia's electronic bill payment system, operated by SADAD Electronic Services Company in partnership with the Saudi Central Bank (SAMA). It enables customers to pay company bills through all Saudi payment channels: online banking, ATMs, payment apps, and MadaPay.
Why does SADAD matter for your business?
- 30+ Saudi banks are connected to the system
- 90%+ of Saudis use digital banking services
- Customers pay without needing a credit card or special account
- Payment is immediate and confirmation is instant for your system
How SADAD Works
SADAD payment flow:
┌──────────────────────────────────────────────┐
│ 1. Your company creates a bill in SADAD │
│ and receives a Bill Number (Biller Code) │
│ ↓ │
│ 2. Customer opens their banking app │
│ → Bill Payments → SADAD → your company │
│ → enters bill number │
│ ↓ │
│ 3. SADAD queries your API for bill details │
│ (Bill Inquiry Request) │
│ ↓ │
│ 4. Customer confirms payment │
│ ↓ │
│ 5. SADAD sends payment notification to your │
│ system (Bill Payment Advice) │
│ ↓ │
│ 6. Your system acknowledges and activates │
│ the service │
└──────────────────────────────────────────────┘
Registration Requirements
To register as a Biller in SADAD:
- Valid Saudi commercial registration
- Saudi IBAN for transfers
- Saudi server (documented IP)
- API endpoint available 24/7
- Valid SSL/TLS
- Agreement with a SADAD Service Provider
or directly with SADAD for large companies
Technical Integration with Laravel
Configuration
// config/sadad.php
return [
'biller_code' => env('SADAD_BILLER_CODE'),
'merchant_id' => env('SADAD_MERCHANT_ID'),
'secret_key' => env('SADAD_SECRET_KEY'),
'base_url' => env('SADAD_BASE_URL'),
];
Bill Inquiry Endpoint
// This endpoint is called by SADAD when a customer wants to pay a bill
class SadadController extends Controller
{
public function inquiry(Request $request): JsonResponse
{
if (!$this->validateRequest($request)) {
return response->json(['status' => '31', 'message' => 'Invalid signature'], 401);
}
$billNumber = $request->input('billNumber');
$invoice = Invoice::where('sadad_bill_number', $billNumber)->first;
if (!$invoice) {
return response->json(['status' => '17', 'message' => 'Bill not found']);
}
if ($invoice->isPaid) {
return response->json(['status' => '16', 'message' => 'Bill already paid']);
}
return response->json([
'status' => '0',
'billNumber' => $billNumber,
'amount' => $invoice->total / 100,
'currency' => 'SAR',
'dueDate' => $invoice->due_at->format('Y-m-d'),
'billInfo' => [
'customerName' => $invoice->user->name,
'serviceType' => $invoice->service_type,
],
]);
}
public function paymentAdvice(Request $request): JsonResponse
{
if (!$this->validateRequest($request)) {
return response->json(['status' => '31'], 401);
}
$billNumber = $request->input('billNumber');
$transactionId = $request->input('transactionId');
$amount = $request->input('amount');
$invoice = Invoice::where('sadad_bill_number', $billNumber)->firstOrFail;
DB::transaction(function use ($invoice, $transactionId, $amount) {
$invoice->markAsPaid;
Payment::create([
'invoice_id' => $invoice->id,
'gateway' => 'sadad',
'transaction_id' => $transactionId,
'amount' => $amount * 100,
'currency' => 'SAR',
'status' => 'completed',
]);
});
ProvisioningService::handlePaymentCompleted($invoice);
return response->json([
'status' => '0',
'billNumber' => $billNumber,
'transactionId' => $transactionId,
]);
}
private function validateRequest(Request $request): bool
{
$signature = $request->header('X-SADAD-Signature');
$expected = hash_hmac('sha256', $request->getContent, config('sadad.secret_key'));
return hash_equals($expected, $signature ?? '');
}
}
Routes
// routes/api.php
Route::prefix('sadad')->group(function {
Route::post('/inquiry', [SadadController::class, 'inquiry'])
->middleware('throttle:100,1');
Route::post('/payment', [SadadController::class, 'paymentAdvice'])
->middleware('throttle:100,1');
});
Testing the Integration
# Simulate an Inquiry request from SADAD
SIGNATURE=$(echo -n '{"billNumber":"12345678"}' | openssl dgst -sha256 -hmac "your_secret" -binary | base64)
curl -X POST https://yoursite.com/api/sadad/inquiry \
-H "Content-Type: application/json" \
-H "X-SADAD-Signature: $SIGNATURE" \
-d '{"billNumber":"12345678"}'
Customer Notifications on Payment
class SadadPaymentReceived extends Notification
{
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Payment Received — cloud.net.sa')
->line("Payment of {$this->amount} SAR received via SADAD")
->line("Invoice ID: {$this->invoice->id}")
->action('View Invoice', route('portal.invoices.show', $this->invoice->id));
}
}
On cloud.net.sa Servers
cloud.net.sa servers in Riyadh and Jeddah are ideal for hosting SADAD integration APIs:
- Static Saudi IP (primary requirement for SADAD)
- uptime عالي مع ضمان تعاقدي — SADAD expects near-complete availability
- Latency very low latency to Saudi banks
- Free SSL and daily backup included
Summary
SADAD opens a payment channel trusted by 30+ million Saudis. The integration is technically straightforward: one endpoint for inquiry and one for payment notification. The primary requirement is a Saudi server and static IP. Meet these two conditions and the integration becomes simple code.
Get Started
Ready to build your project? Configure your cloud server with the specs your app needs, or view plans and pricing.
