How to Prepare Your Server for Nafath Integration

Saudi server + SSL + static IP + specific security configurations. Technical steps from application to go-live.
Introduction to Nafath Integration
Nafath is the only way to verify the identity of Saudi citizens and residents digitally. Integrating with it opens the ability to serve thousands of customers without in-person visits or paper documents.
This guide explains the technical integration step by step — from server setup to writing code.
Prerequisites Before Starting
1. Server in Saudi Arabia with a static dedicated IP
(See the article on Nafath server requirements)
2. Official accreditation from CST:
- Client ID
- Client Secret
3. Active ZATCA registration (for some integration types)
4. HTTPS with a valid certificate from an approved CA
How Nafath Works
Verification flow:
┌─────────────────────────────────────────────┐
│ 1. User requests login/verification │
│ ↓ │
│ 2. Your app sends a request to Nafath API │
│ {nationalId, random, service} │
│ ↓ │
│ 3. Nafath sends notification to user's │
│ mobile (Nafath app) │
│ ↓ │
│ 4. User approves on the app │
│ ↓ │
│ 5. Your app polls for approval result │
│ ↓ │
│ 6. On approval: complete authentication │
└─────────────────────────────────────────────┘
Practical Integration with Laravel
Installation and Setup
# No official library yet - using HTTP Client
composer require guzzlehttp/guzzle
// config/nafath.php
return [
'base_url' => env('NAFATH_BASE_URL', 'https://nafath.api.iam.gov.sa/v1'),
'app_id' => env('NAFATH_APP_ID'),
'app_key' => env('NAFATH_APP_KEY'),
'service_code' => env('NAFATH_SERVICE_CODE'),
'timeout' => 30,
];
Integration Service
// app/Services/NafathService.php
class NafathService
{
public function initiateVerification(string $nationalId, string $random): array
{
$response = Http::timeout(config('nafath.timeout'))
->withHeaders([
'APP-ID' => config('nafath.app_id'),
'APP-KEY' => config('nafath.app_key'),
'Content-Type' => 'application/json',
])
->post(config('nafath.base_url') . '/moi/checkStatus', [
'nationalId' => $nationalId,
'random' => $random,
'serviceCode' => config('nafath.service_code'),
]);
if ($response->failed) {
throw new NafathException("Nafath API error: {$response->status}");
}
return $response->json;
}
public function checkStatus(string $nationalId, string $random, string $transId): array
{
return Http::withHeaders([
'APP-ID' => config('nafath.app_id'),
'APP-KEY' => config('nafath.app_key'),
])->get(config('nafath.base_url') . '/moi/checkStatus', [
'nationalId' => $nationalId,
'random' => $random,
'transId' => $transId,
])->json;
}
public function generateRandom: string
{
return strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));
}
}
Controller
class NafathController extends Controller
{
public function initiate(Request $request): JsonResponse
{
$request->validate(['national_id' => ['required', 'digits:10']]);
$random = $this->nafath->generateRandom;
$result = $this->nafath->initiateVerification($request->national_id, $random);
session([
'nafath_national_id' => $request->national_id,
'nafath_random' => $random,
'nafath_trans_id' => $result['transId'],
'nafath_expires_at' => now->addMinutes(5),
]);
return response->json([
'random' => $random,
'trans_id' => $result['transId'],
]);
}
public function status: JsonResponse
{
if (now->isAfter(session('nafath_expires_at'))) {
return response->json(['status' => 'EXPIRED']);
}
$result = $this->nafath->checkStatus(
session('nafath_national_id'),
session('nafath_random'),
session('nafath_trans_id')
);
return response->json(['status' => $result['status']]);
}
}
Security and Compliance
// Rate limiting to prevent abuse
Route::middleware(['throttle:5,1'])->group(function {
Route::post('/nafath/initiate', [NafathController::class, 'initiate']);
});
// Log all verification attempts (hash national ID, never store plaintext)
Log::channel('nafath')->info('Verification attempt', [
'national_id_hash' => hash('sha256', $nationalId),
'ip' => request->ip,
'timestamp' => now->toISOString,
]);
Testing the Integration
# Test in Sandbox
curl -X POST https://nafath.api.iam.gov.sa/sandbox/moi/checkStatus \
-H "APP-ID: test_app_id" \
-H "APP-KEY: test_app_key" \
-H "Content-Type: application/json" \
-d '{
"nationalId": "1234567890",
"random": "ABCD1234",
"serviceCode": "TST"
}'
On cloud.net.sa Servers
cloud.net.sa servers in Riyadh and Jeddah meet all Nafath requirements. With dedicated IP availability and a SLA عالي مع ضمان تعاقدي, your application can obtain production accreditation without technical obstacles.
Summary
Nafath integration is not technically complex — it is a straightforward HTTP API. The real challenge is compliance: Saudi server, static IP, HTTPS, and audit logs. Get these fundamentals right and the integration will work smoothly.
Get Started
Want to try it yourself? Choose the right hosting plan or build your custom server with the specs you need.
