VeTims
Reference

Every field, in one place.

The request body, the fiscal block that comes back, and the headers on a delivery. The narrative version lives on how it works.

Request

What you send

invoice

client_reference required Your own invoice number, and the key we deduplicate on.
type sale, or credit_note to refund one. Defaults to sale.
original_client_reference The sale a credit note refunds. Required with one.
device_serial Which registered device signs it. Only needed once you have more than one.
payment_type_code KRA's payment type, 01 to 07. Defaults to 01.

customer

customer.pin · name · phone The buyer. The PIN is what lets them claim the input VAT back.

lines[]

description required What was sold.
unit_price required VAT-inclusive — the price on the shelf.
quantity Defaults to 1.
tax_category A to E. Defaults to B, the 16% standard rate.
discount_amount Taken off the line before the tax is split out.
item_code Your own item code.
item_classification_code KRA's classification code for the item.
Response

What comes back

The same shape on the POST, the poll and the webhook. The tax split is computed for you; the fiscal block appears once KRA has signed it.

invoice

client_reference Your own reference, echoed back.
invoice_number The device's invoice number — the uninterrupted ascending series KRA requires.
status pending, signing, signed or failed.
failure_reason Present when status is failed. KRA's own message, verbatim.

totals

taxable_by_category VAT-exclusive taxable amount, keyed A to E.
tax_by_category VAT charged, keyed A to E.
total_taxable · total_tax · total_amount Invoice totals. total_amount is VAT-inclusive — what the customer pays.

lines[]

taxable_amount · tax_amount · total_amount The same split per line, plus everything you sent on it.

fiscal

cu_invoice_number The control unit invoice number. Printed as issued.
signature The receipt signature, printed exactly as returned.
internal_data The internal data block that goes with it.
qr_payload Encoded as the QR. It resolves to verification_url on KRA.
verification_url Where the QR sends whoever scans it.
scu_id · scu_receipt_number Which control unit signed it, and its receipt number.
scu_timestamp · signed_at When KRA signed it, and when we recorded that.
Deliveries

Headers and codes

Webhook headers

X-Etims-Event invoice.signed or invoice.failed.
X-Etims-Timestamp Unix seconds. Reject anything too old to be yours.
X-Etims-Signature sha256=… HMAC over "{timestamp}.{raw body}".

Status codes

202 Accepted The invoice was recorded and queued for signing.
200 OK A replay of a client_reference we already hold. Same invoice, no second signature.
401 Unauthorized The Bearer token is missing, malformed or revoked.
403 Forbidden The tenant is suspended.
404 Not Found On the poll: no invoice with that client_reference under your token.
422 Unprocessable The body failed validation. The response names the field.
429 Too Many Requests Over 120 requests a minute for this tenant.

Verifying a delivery

Sign the timestamp and the raw body with the secret issued when you register your URL, compare in constant time, and reject anything whose timestamp is too old to be yours.

Verify
$ts = $request->header('X-Etims-Timestamp');
$body = $request->getContent();

$expected = hash_hmac(
    'sha256', $ts.'.'.$body, $secret
);

hash_equals(
    'sha256='.$expected,
    $request->header('X-Etims-Signature')
);

← Back to how it works

Want a sandbox token against these shapes?

Tell us what your system does. We reply with a token, a registered device and a signed example invoice.

Start a pilot +254 764 444 408