Acumatica Kenya PAYE Payroll Setup — A Complete Guide is one of those projects that every Acumatica implementer in Africa eventually takes on, and one that no two countries do the same way. The tax authority's API, the device registration flow, the receipt format, the offline tolerance — all vary. This guide is the field-tested pattern for Kenya, with the regulatory specifics, the integration architecture, and the operational hygiene that keeps the KRA happy and the finance team sane.
The regulatory context
The regulatory framework is set by the tax authority. Every detail — what triggers a submission, what format the receipt must take, what happens when the device is offline — flows from that framework. Skim the official documentation before you write any code. The cost of getting it wrong is a fine, a failed audit, or both.
| Concept | What it means |
|---|---|
| Device | A registered point-of-sale or invoice issuance point |
| Receipt | A fiscal document with a unique number and a signed payload |
| Fiscal day | The period between opening and closing of a device |
| Offline mode | Operation when the device cannot reach the tax authority's servers |
| Audit file | The export of all fiscal receipts for a given period |
The API is a reflection of the regulation. If the API behaves in a way that seems strange, it is because the regulation requires it. The fastest way to debug a fiscalisation integration is to re-read the relevant section of the regulation, not to re-read the API docs.
What Acumatica brings to the table
Acumatica's tax engine handles the data model — tax zones, tax categories, tax schedules, the calculation per line. The fiscalisation layer is yours to build on top. The integration sits between Acumatica and the tax authority's API; the finance team works in Acumatica as normal.
public class AcumaticaKenyaPayrollPayeSetupService
{
private readonly HttpClient _http;
private readonly AcumaticaClient _acumatica;
public async Task<FiscalReceipt> IssueReceipt(ARInvoice invoice)
{
var receipt = MapToFiscalReceipt(invoice);
var response = await _http.PostAsJsonAsync("/api/receipts", receipt);
invoice.GetExtension<ARInvoiceExt>().UsrFiscalReceiptNbr = response.ReceiptNbr;
invoice.GetExtension<ARInvoiceExt>().UsrFiscalSignature = response.Signature;
invoice.GetExtension<ARInvoiceExt>().UsrFiscalQR = response.QRCode;
await _acumatica.Update(invoice);
return response;
}
}
For the broader Acumatica REST patterns, see the REST API definitive guide.
Device registration
Every Kenya fiscalisation integration starts with device registration. The flow:
- Generate a device key pair (or use the authority's).
- Submit the public key, the device serial, and the taxpayer TIN to the authority.
- Receive a device ID and a certificate back.
- Store the certificate and the private key in a vault.
- Open a fiscal day on the device before any receipts are issued.
public async Task<Device> RegisterDevice(string tin, string serial)
{
var keyPair = RsaKeyPair.Generate(2048);
var registration = new { TIN = tin, SerialNbr = serial, PublicKey = keyPair.PublicKeyPem };
var resp = await _http.PostAsJsonAsync("/api/devices/register", registration);
var device = await resp.Content.ReadFromJsonAsync<Device>();
_vault.Store($"device:{device.DeviceID}:privateKey", keyPair.PrivateKeyPem);
_vault.Store($"device:{device.DeviceID}:certificate", device.Certificate);
return device;
}
The private key signs every fiscal receipt. If it leaks, the device is compromised and the taxpayer is liable. Azure Key Vault, AWS Secrets Manager, HashiCorp Vault — all fine. The key never lives in code, config files, or environment variables.
The fiscal day
A fiscal day is the period between opening and closing of a device. Within a fiscal day, receipts are numbered sequentially; cross-day numbering is not allowed. The integration must:
- Open a fiscal day on the first receipt of the day (or on schedule, if the device is shared).
- Close the fiscal day at end of day (or on demand).
- Number receipts within the day sequentially.
- Handle the case where the day was never opened (raise an alert).
public async Task OpenFiscalDay(Device device, DateTime date)
{
var resp = await _http.PostAsJsonAsync($"/api/devices/{device.DeviceID}/days", new { Date = date });
var day = await resp.Content.ReadFromJsonAsync<FiscalDay>();
_state.SetOpenDay(device.DeviceID, day);
}
public async Task CloseFiscalDay(Device device, FiscalDay day)
{
var resp = await _http.PostAsync($"/api/devices/{device.DeviceID}/days/{day.DayID}/close", null);
_state.ClearOpenDay(device.DeviceID);
}
Offline mode
Most fiscalisation frameworks allow offline operation for a limited period, after which the device must reconnect and submit the backlog. The integration must handle the transition cleanly.
| State | Action |
|---|---|
| Online | Submit each receipt in real time |
| Offline (transient) | Buffer locally, submit when online |
| Offline (extended) | Alert, continue buffering, escalate |
| Backlog | Drain in batches, in order, with retries |
public class OfflineBuffer
{
private readonly Queue<FiscalReceipt> _buffer = new();
public async Task Submit(FiscalReceipt receipt)
{
if (IsOnline())
{
try { await _http.Submit(receipt); }
catch (NetworkException)
{
_buffer.Enqueue(receipt);
_state.SetOffline();
}
}
else { _buffer.Enqueue(receipt); }
}
public async Task Drain()
{
while (_buffer.TryDequeue(out var receipt)) await _http.Submit(receipt);
}
}
The offline buffer must be sized for the worst-case outage. A buffer of 10,000 receipts is usually enough; 100,000 is safe. Beyond that, the system should fail loudly — better to stop issuing receipts than to silently lose them.
Credit notes and refunds
Credit notes are not just reverses; they are fiscal documents in their own right. The integration must:
- Reference the original fiscal receipt number.
- Generate a new fiscal receipt for the credit note.
- Maintain the audit trail (which credit note for which receipt).
- Submit the credit note to the authority; do not modify the original.
public async Task<FiscalReceipt> IssueCreditNote(ARInvoice creditNote, ARInvoice original)
{
var ext = original.GetExtension<ARInvoiceExt>();
var receipt = new FiscalReceipt
{
Type = "CreditNote",
OriginalReceiptNbr = ext.UsrFiscalReceiptNbr,
OriginalSignature = ext.UsrFiscalSignature,
};
var response = await _http.PostAsJsonAsync("/api/receipts", receipt);
creditNote.GetExtension<ARInvoiceExt>().UsrFiscalReceiptNbr = response.ReceiptNbr;
creditNote.GetExtension<ARInvoiceExt>().UsrFiscalSignature = response.Signature;
creditNote.GetExtension<ARInvoiceExt>().UsrReferenceOriginal = ext.UsrFiscalReceiptNbr;
return response;
}
Reconciliation
At month end, the integration must reconcile the Acumatica records with the authority's records. The checks:
- Every Acumatica invoice has a fiscal receipt number.
- Every fiscal receipt number on the authority's side has an Acumatica record.
- The totals match (Acumatica total = authority total).
- There are no receipts in offline buffer that have not been submitted.
-- Find Acumatica invoices without a fiscal receipt
SELECT ar.RefNbr, ar.DocDate, ar.CuryOrigDocAmt
FROM ARInvoice ar
LEFT JOIN ARInvoiceExt ext ON ar.NoteID = ext.NoteID
WHERE ar.Released = 1
AND ar.DocDate BETWEEN '2026-01-01' AND '2026-01-31'
AND (ext.UsrFiscalReceiptNbr IS NULL OR ext.UsrFiscalReceiptNbr = '');
For the broader data reconciliation patterns, see the data migration guide.
Common failures
The five failures I have seen, in order of how often they happen:
- Expired device certificate. Certificates have a validity period; the renewal must be scheduled.
- Offline buffer overflow. A long network outage; the buffer fills; the device stops issuing receipts.
- Clock drift. The device clock is wrong; the authority rejects the receipt. Use NTP.
- Duplicate receipts. A retry that was not idempotent; the authority rejects the second submission. Use the natural key as the idempotency token.
- Schema change. The authority updates the API; the integration breaks. Subscribe to the authority's developer newsletter.
Wrapping up
Kenya's fiscalisation is not a weekend project. The integration touches the device, the certificate, the fiscal day, the receipts, the credit notes, the offline buffer, the reconciliation, and the audit trail. Get all eight right and the authority is happy, the auditor is happy, and the finance team is happy. Skip any one and you are debugging in production with a deadline.
For the broader tax engine configuration, see the multi-country tax engine guide. For the related reading, see the Zimbabwe ZIMRA FDMS guide and the KRA eTIMS guide.