SubmitPayrollBatch
https://test.api.neotek.sa/b2b-unified/v1/payroll/submitSandbox 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:
| Parameter | Required | Description |
|---|---|---|
bankCode | Required | which bank executes the batch: RJHISARI or ARNBSARI |
payrollDate | Required | the date the salaries are paid |
currency | Required | for example SAR |
debtorAccountIban | Required | the account the salaries leave |
debtorName | Required | the employer |
debtorAddressLine1, debtorAddressLine2, debtorAddressLine3 | Required | — |
transactions | Required | the employees, as an array with at least one entry |
batchDescription | Optional | up to 35 characters. Generated as Payroll YYYY-MM-DD if omitted |
Each entry in transactions:
| Parameter | Required | Description |
|---|---|---|
beneficiaryBankBic | Required | the employee's bank, exactly 8 characters |
beneficiaryAccountIban | Required | must be a Saudi IBAN |
salaryAmount | Required | what the employee is paid |
employeeId | Required | exactly 10 digits |
basicSalary | Required | — |
housingAllowance | Required | — |
otherEarnings | Required | — |
salaryDeductions | Required | — |
beneficiaryName | Optional | — |
beneficiaryAddressLine1, beneficiaryAddressLine2, beneficiaryAddressLine3 | Optional | — |
The salary components must reconcile. basicSalary + housingAllowance + otherEarnings − salaryDeductions must equal salaryAmount. The bank validates this arithmetic and rejects the batch if it does not balance.
Headers:
| Parameter | Required | Description |
|---|---|---|
authorization | Required | Bearer <ACCESS_TOKEN> |
content-type | Required | application/json |
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
{ "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
| Status | Codes | Cause on this endpoint |
|---|---|---|
400 ERROR_CATEGORY_VALIDATION_FAILED | INVALID_IBAN, INVALID_AMOUNT, INVALID_ACCOUNT, INVALID_CURRENCY, INVALID_BANK_CODE, INVALID_REFERENCE, BENEFICIARY_NOT_FOUND | Non-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_FAILED | AUTH_FAILED, CREDENTIALS_NOT_FOUND, UNAUTHORIZED | Token expired, or no vault credentials for your app with that bank |
409 ERROR_CATEGORY_DUPLICATE | DUPLICATE_REFERENCE, DUPLICATE_TRANSACTION, DUPLICATE_PAYMENT | The batch was already accepted — see above |
422 ERROR_CATEGORY_BANK_REJECTED | INSUFFICIENT_FUNDS, AMOUNT_LIMIT_EXCEEDED, DAILY_LIMIT_EXCEEDED, INVALID_PAYMENT_DATE, ACCOUNT_BLOCKED, ACCOUNT_CLOSED, PAYMENT_NOT_ALLOWED | The 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_FAILURE | SYSTEM_ERROR, INTERNAL_ERROR | Gateway-side failure |
503 ERROR_CATEGORY_TEMPORARY_FAILURE | SERVICE_UNAVAILABLE, BANK_UNAVAILABLE, NETWORK_ERROR, NETWORK_TIMEOUT, REQUEST_TIMEOUT | Bank unreachable — retry with backoff, which is safe because of the idempotency guarantee |