writing/blog/2026/08
BlogAug 10, 2026·6 min read

Mudad Integration Guide: Automating Saudi Payroll Compliance

How to build a reliable integration layer between your ERP or HR system and Mudad for automated WPS file uploads, compliance tracking, and rejection prevention.

Every payroll cycle in a Saudi establishment ends with the same critical moment: was the file uploaded to Mudad? Were the records accepted? Is the compliance rate high enough to protect the Saudization certificate?

The problem is rarely the payroll itself. It is the gap between your HR or accounting system and the Mudad platform. Every establishment solves this gap its own way — manual Excel, CSV export, copy-paste. These all work, until they do not, and someone discovers that three employees have had no record submitted for two months.

This guide explains how to build a reliable integration layer between your software systems and Mudad, instead of relying on manual processes that break at the worst possible time.

How Mudad Works

Mudad is a licensed fintech platform backed by the Saudi Ministry of Human Resources and Social Development (HRSD), the General Organization for Social Insurance (GOSI), and the Saudi Central Bank (SAMA). It operates as two interconnected systems:

Mudad Business — A payroll management system for small and medium enterprises. It enables direct salary transfers via bank integration and automatically uploads the WPS file upon transfer completion.

Mudad Compliance — A WPS file upload system for establishments using external payroll or ERP software. Files are uploaded in CSV format per HRSD specifications, and Mudad cross-references them against GOSI and bank records.

The compliance flow:

Payroll System → Generate WPS file → Mudad → Match against GOSI + banks → Compliance report

Establishments that transfer directly through Mudad Business bypass the upload step. Any establishment using an external accounting or ERP system needs the file upload path — and this is where most errors occur.

What Integration Actually Requires

Mudad does not publish a public API. Programmatic integration is available to certified technology partners like ZenHR and Jisr through a partner agreement. For other systems, the practical path is:

  1. Build the correct file to Mudad's specifications
  2. Upload the file through the Mudad interface or SFTP depending on establishment size
  3. Read the compliance report and reprocess rejected records

The most common mistake: systems that produce a correctly formatted file but contain internal data mismatches — IQAMA numbers, salary amounts, IBAN numbers — that do not match GOSI records. The file is accepted. The records are rejected. The result is a violation you only discover when the monthly report appears.

Building a Validation Layer Before Upload

The real investment is not in uploading the file — it is in validating it before it reaches Mudad. Here is a TypeScript implementation of the core validation logic:

interface MudadEmployee {
  iqamaOrNationalId: string;   // exactly 10 digits
  employeeName: string;
  bankAccountIBAN: string;     // SA + 22 digits
  basicSalary: number;
  allowances: number;
  deductions: number;
  netSalary: number;
  paymentMonth: string;        // YYYY-MM
}
 
interface ValidationResult {
  isValid: boolean;
  errors: string[];
  employeeId: string;
}
 
function validateEmployee(emp: MudadEmployee): ValidationResult {
  const errors: string[] = [];
 
  if (!/^\d{10}$/.test(emp.iqamaOrNationalId)) {
    errors.push(`Invalid ID number: ${emp.iqamaOrNationalId}`);
  }
 
  if (!/^SA\d{22}$/.test(emp.bankAccountIBAN)) {
    errors.push(`Invalid IBAN format: ${emp.bankAccountIBAN}`);
  }
 
  const calculatedNet = emp.basicSalary + emp.allowances - emp.deductions;
  if (Math.abs(calculatedNet - emp.netSalary) > 0.01) {
    errors.push(
      `Salary imbalance: ${emp.basicSalary} + ${emp.allowances} - ${emp.deductions} = ${calculatedNet} does not equal ${emp.netSalary}`
    );
  }
 
  if (emp.netSalary <= 0) {
    errors.push(`Net salary must be positive`);
  }
 
  return {
    isValid: errors.length === 0,
    errors,
    employeeId: emp.iqamaOrNationalId,
  };
}
 
function validatePayrollBatch(employees: MudadEmployee[]): {
  valid: MudadEmployee[];
  invalid: Array<{ employee: MudadEmployee; errors: string[] }>;
  summary: string;
} {
  const valid: MudadEmployee[] = [];
  const invalid: Array<{ employee: MudadEmployee; errors: string[] }> = [];
 
  for (const emp of employees) {
    const result = validateEmployee(emp);
    if (result.isValid) {
      valid.push(emp);
    } else {
      invalid.push({ employee: emp, errors: result.errors });
    }
  }
 
  const rate = ((valid.length / employees.length) * 100).toFixed(1);
  const summary = `${valid.length}/${employees.length} records valid (${rate}%)`;
 
  return { valid, invalid, summary };
}

This layer catches problems before upload — not after.

Generating the WPS File to Mudad's Specification

After validation, the next step is generating the file in the format Mudad expects:

import { createObjectCsvWriter } from 'csv-writer';
import * as path from 'path';
 
async function generateMudadWPSFile(
  employees: MudadEmployee[],
  establishmentId: string,
  paymentMonth: string,
  outputDir: string
): Promise<string> {
  const fileName = `WPS_${establishmentId}_${paymentMonth.replace('-', '')}.csv`;
  const filePath = path.join(outputDir, fileName);
 
  const csvWriter = createObjectCsvWriter({
    path: filePath,
    header: [
      { id: 'EmployeeID', title: 'EmployeeID' },
      { id: 'EmployeeName', title: 'EmployeeName' },
      { id: 'IBAN', title: 'IBAN' },
      { id: 'BasicSalary', title: 'BasicSalary' },
      { id: 'HousingAllowance', title: 'HousingAllowance' },
      { id: 'OtherAllowances', title: 'OtherAllowances' },
      { id: 'Deductions', title: 'Deductions' },
      { id: 'NetSalary', title: 'NetSalary' },
      { id: 'PaymentDate', title: 'PaymentDate' },
    ],
    encoding: 'utf8',
  });
 
  const records = employees.map((emp) => ({
    EmployeeID: emp.iqamaOrNationalId,
    EmployeeName: emp.employeeName,
    IBAN: emp.bankAccountIBAN,
    BasicSalary: emp.basicSalary.toFixed(2),
    HousingAllowance: '0.00',
    OtherAllowances: emp.allowances.toFixed(2),
    Deductions: emp.deductions.toFixed(2),
    NetSalary: emp.netSalary.toFixed(2),
    PaymentDate: new Date().toISOString().split('T')[0],
  }));
 
  await csvWriter.writeRecords(records);
  return filePath;
}

The Most Common Silent Failure Mode

Establishments relying on manual file upload often encounter the same trap: the system logs a successful upload, but the compliance report shows a lower-than-expected rate. The causes follow a consistent pattern:

ID numbers that do not match GOSI records — An employee's IQAMA number was updated in the HR system, but the old number still exists in the social insurance registry. This requires updating the establishment's GOSI records first, not just the internal system.

IBAN stored in incorrect format — Some systems store IBANs without the "SA" prefix or with spaces. The file exports with the right column structure but the underlying data is wrong.

Net salary does not match the actual bank transfer — If the establishment absorbed bank transfer fees and they were deducted from the employee's amount, there is a mismatch between the file and what the employee received.

All three produce silently rejected records — the file is accepted, the compliance rate drops, and no error appears until the monthly report. For a deeper look at the reconciliation logic Mudad applies, see our article on why Saudi WPS violations start in your HR data, not in payroll.

Monitoring Compliance Status

After upload, the most important step is reading the compliance report and triggering immediate alerts when the rate drops:

interface ComplianceReport {
  establishmentId: string;
  paymentMonth: string;
  totalEmployees: number;
  acceptedRecords: number;
  rejectedRecords: number;
  complianceRate: number;
  violations: Array<{
    employeeId: string;
    reason: string;
    correctionDeadline: string;
  }>;
}
 
function analyzeComplianceReport(report: ComplianceReport): {
  status: 'compliant' | 'at-risk' | 'violation';
  message: string;
  requiredAction: string;
} {
  const { complianceRate, violations } = report;
 
  // WPS program threshold: 95% compliance required
  if (complianceRate >= 95) {
    return {
      status: 'compliant',
      message: `Compliance rate ${complianceRate}% — no violations`,
      requiredAction: 'No action required',
    };
  }
 
  if (complianceRate >= 80) {
    const deadline = violations[0]?.correctionDeadline ?? 'unspecified';
    return {
      status: 'at-risk',
      message: `Compliance rate ${complianceRate}% — ${violations.length} records need review`,
      requiredAction: `Correct rejected records before ${deadline}`,
    };
  }
 
  return {
    status: 'violation',
    message: `Compliance rate ${complianceRate}% — active violation`,
    requiredAction: 'Alert HR management immediately and file correction or appeal',
  };
}

Connecting Mudad to the Broader Saudi Compliance Stack

Mudad is not the only platform in the Saudi compliance equation. If you are building a comprehensive integration layer, you need to connect it with:

  • Qiwa / Nitaqat — for tracking Saudization ratios and linking them to WPS data. See our guide on integrating Qiwa with HR systems for Saudi Nitaqat compliance.
  • ZATCA / Fatoorah — for synchronizing payroll data with e-invoicing in establishments that track labor cost allocation. See our Fatoorah integration guide.
  • End-of-service benefit calculations — incomplete monthly payroll records affect end-of-service entitlements during audits.

Based on what we see across Saudi establishments, the most reliable architecture:

ERP / HR System
    ↓ export payroll data (JSON or database)
Validation layer
    ↓ filter invalid records + immediate notification
WPS file generator
    ↓ CSV file to Mudad specification
Upload to Mudad (manual or SFTP)
    ↓ upload confirmation
Compliance monitor
    ↓ read compliance report periodically
Internal dashboard
    ↓ immediate alert on compliance rate drop

The critical element: the validation layer runs before upload, not after. Every record rejected after upload means time spent on correction and resubmission — time you may not have before the compliance window closes.

If You Are a Technology Partner Building a Product

If you are building an HR or accounting system targeting the Saudi market, Mudad integration is not an optional feature — it is a prerequisite for acquiring clients in this market. ZenHR and Jisr both market their Mudad integration as a primary selling point.

The official partnership path begins with direct contact with the Mudad technical team to obtain partner API credentials. For most systems outside that program, the practical route is a high-quality WPS file generator with a clean upload interface and compliance tracking.

Conclusion

Mudad is not technically complex. Establishments that struggle with it usually struggle not with Mudad itself but with the gap between their internal system and the file it produces. Building a solid validation layer before upload, and real monitoring of compliance reports after it, eliminates most problems before they become violations.

If you are building a programmatic integration with Mudad or the broader Saudi compliance stack, speak with the Noqta team — we design the architecture and build the layers that prevent violations before they occur.