CLOUD
Guides

How to Prepare Your Server for Nafath Integration

CLOUD|February 26, 2026|3 min read|671 views
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.

Share