Back to neotek ↗Get Sandbox Access

SubmitPayrollBatch

POSThttps://test.api.neotek.sa/b2b-unified/v1/payroll/submit

Sandbox URL · in Production call https://api.neotek.sa

Sends a WPS-compliant salary batch. Every employee runs against the same bank and debtor account, so those sit at batch level.

Body — the batch:

ParameterRequiredDescription
bankCodeRequiredwhich bank executes the batch: RJHISARI or ARNBSARI
payrollDateRequiredthe date the salaries are paid
currencyRequiredfor example SAR
debtorAccountIbanRequiredthe account the salaries leave
debtorNameRequiredthe employer
debtorAddressLine1, debtorAddressLine2, debtorAddressLine3Required
transactionsRequiredthe employees, as an array with at least one entry
batchDescriptionOptionalup to 35 characters. Generated as Payroll YYYY-MM-DD if omitted

Each entry in transactions:

ParameterRequiredDescription
beneficiaryBankBicRequiredthe employee's bank, exactly 8 characters
beneficiaryAccountIbanRequiredmust be a Saudi IBAN
salaryAmountRequiredwhat the employee is paid
employeeIdRequiredexactly 10 digits
basicSalaryRequired
housingAllowanceRequired
otherEarningsRequired
salaryDeductionsRequired
beneficiaryNameOptional
beneficiaryAddressLine1, beneficiaryAddressLine2, beneficiaryAddressLine3Optional

The salary components must reconcile. basicSalary + housingAllowance + otherEarningssalaryDeductions must equal salaryAmount. The bank validates this arithmetic and rejects the batch if it does not balance.

Headers:

ParameterRequiredDescription
authorizationRequiredBearer <ACCESS_TOKEN>
content-typeRequiredapplication/json
cURL
curl -X POST 'https://test.api.neotek.sa/b2b-unified/v1/payroll/submit' \  --header 'authorization: Bearer <ACCESS_TOKEN>' \  --header 'content-type: application/json' \  --data '{    "bankCode": "ARNBSARI",    "payrollDate": "2026-04-20",    "currency": "SAR",    "debtorAccountIban": "SA1030000012345678901004",    "debtorName": "Neotek Trading Co",    "debtorAddressLine1": "Building 123, King Fahd Road",    "debtorAddressLine2": "Al Olaya District",    "debtorAddressLine3": "Riyadh, Saudi Arabia",    "batchDescription": "April 2026 Payroll",    "transactions": [      {        "beneficiaryBankBic": "RJHISARI",        "beneficiaryAccountIban": "SA1080000012345678901003",        "beneficiaryName": "Ahmed Hassan",        "salaryAmount": "8500.00",        "employeeId": "2019092502",        "basicSalary": "5000.00",        "housingAllowance": "2000.00",        "otherEarnings": "1500.00",        "salaryDeductions": "0.00"      }    ]  }'

Response — keep payrollReference exactly as returned; it is the key for both status and the WPS download

JSON
{  "success": true,  "code": "PAYMENT_ACCEPTED",  "message": "Payroll batch accepted for processing",  "payrollReference": "c20423c3-5df2-465b-9dd2-ca1614aa9463",  "paymentStatus": "PAYMENT_STATUS_ACCEPTED",  "statusMessage": "Payroll queued successfully for processing"}

409 DUPLICATE_REFERENCE is a guarantee, not a failure. The same employee set on the same payrollDate produces a deterministic batch reference, so if the bank already accepted that batch you get a 409 rather than a second payment run. That is what makes retrying after a network timeout safe.

Errors — each failure carries an error category and a specific code

StatusCodesCause on this endpoint
400 ERROR_CATEGORY_VALIDATION_FAILEDINVALID_IBAN, INVALID_AMOUNT, INVALID_ACCOUNT, INVALID_CURRENCY, INVALID_BANK_CODE, INVALID_REFERENCE, BENEFICIARY_NOT_FOUNDNon-Saudi beneficiary IBAN, BIC not 8 characters, employeeId not exactly 10 digits, salary components that do not reconcile, empty transactions, a field over its length limit
401 ERROR_CATEGORY_AUTH_FAILEDAUTH_FAILED, CREDENTIALS_NOT_FOUND, UNAUTHORIZEDToken expired, or no vault credentials for your app with that bank
409 ERROR_CATEGORY_DUPLICATEDUPLICATE_REFERENCE, DUPLICATE_TRANSACTION, DUPLICATE_PAYMENTThe batch was already accepted — see above
422 ERROR_CATEGORY_BANK_REJECTEDINSUFFICIENT_FUNDS, AMOUNT_LIMIT_EXCEEDED, DAILY_LIMIT_EXCEEDED, INVALID_PAYMENT_DATE, ACCOUNT_BLOCKED, ACCOUNT_CLOSED, PAYMENT_NOT_ALLOWEDThe payroll account lacks funds for the batch total, payrollDate falls outside the bank's window, or the debtor account is blocked
500 ERROR_CATEGORY_SYSTEM_FAILURESYSTEM_ERROR, INTERNAL_ERRORGateway-side failure
503 ERROR_CATEGORY_TEMPORARY_FAILURESERVICE_UNAVAILABLE, BANK_UNAVAILABLE, NETWORK_ERROR, NETWORK_TIMEOUT, REQUEST_TIMEOUTBank unreachable — retry with backoff, which is safe because of the idempotency guarantee