VeTims
How it works

Four steps, one integration.

Two endpoints and one webhook URL. Teams typically have a signed sandbox invoice on day one.

1

Send the sale.

POST /api/v1/invoices

POST one JSON invoice. Idempotent on your own invoice number — bad network, double-click, retry storm: it signs once.

2

We sign with KRA.

OSCU / VSCU

Queued, retried and reconciled. A slow KRA never blocks your till.

3

Print what comes back.

invoice.signed

Signature, internal data, CU invoice number and QR payload, delivered by signed webhook or poll — everything the receipt must carry.

4

Anyone verifies it against KRA.

QR

Your customer scans the QR on the receipt and checks the invoice against KRA. Every invoice, device and webhook delivery stays visible in the operator console.

Where each field ends up

Printed as issued — an inspector reads them off the paper.

cu_invoice_number The control unit invoice number.
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.
Request
POST /api/v1/invoices
{
  "client_reference": "INV-2026-08-0042",
  "customer": { "pin": "A001234567B" },
  "lines": [
    { "description": "Fresh Fri 1L", "quantity": 2,
      "unit_price": 339.00, "tax_category": "B" }
  ]
}
Webhook
{
  "event": "invoice.signed",
  "timestamp": 1786446455,
  "data": {
    "client_reference": "INV-2026-08-0042",
    "status": "signed",
    "fiscal": {
      "cu_invoice_number": "KRACU0300000021/42",
      "signature": "V249-J39C-FJ48-HE2W",
      "qr_payload": "11082026#140735#…",
      "verification_url": "https://etims.kra.go.ke/…"
    }
  }
}
The surface

Two endpoints, one webhook URL

POST /api/v1/invoices

Records the sale and queues it for signing. 202 the first time, 200 on any replay of the same client_reference.

GET /api/v1/invoices/{reference}

Status poll for systems that would rather not receive webhooks. The fiscal block appears once the invoice is signed.

invoice.signed · invoice.failed

The two webhook events. Both carry the full invoice, so a signed webhook alone is enough to print the receipt.

Bearer token per tenant · 120 requests per minute · sandbox and production on the same shapes

The invoice

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.
Failure

What happens when something breaks

KRA rejects the invoice

A business rejection is permanent, so we do not retry it. The invoice is marked failed with KRA's own result code and message, and invoice.failed fires immediately.

KRA is slow or unreachable

Transport failures get five attempts, backing off 10s, 30s, 1m, 2m, 5m. Your till never waits on it.

Your endpoint is down

Webhooks get six attempts, backing off 30s, 2m, 10m, 30m, 1h. Every attempt is logged with its response code, and the poll endpoint always holds the same answer.

You send the same sale twice

The second POST returns 200 with the first invoice. One client_reference, one signature, one entry in the KRA sequence.

Webhooks

Verifying a delivery

Every delivery is signed with HMAC-SHA256 over the timestamp and the raw body, using the secret issued when you register your URL. Compare in constant time, and reject anything whose timestamp is too old to be yours.

X-Etims-Event
X-Etims-Timestamp
X-Etims-Signature

Verify
$expected = hash_hmac(
    'sha256',
    $request->header('X-Etims-Timestamp').'.'.$request->getContent(),
    $secret
);

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

What you get on day one

Idempotent invoice API

Send a sale twice, it signs once. Your reference is the key.

Server-side tax engine

Categories A (exempt), B (16%), C (zero-rated), D (non-VAT), E (8%) computed from VAT-inclusive prices; five VAT classes never enter your codebase.

Spec-exact credit notes

Refunds reference the original signed sale, carry negative amounts, and follow KRA's cancel-once rules automatically.

Signed webhooks

HMAC-SHA256 over a timestamped body; retried from 30 seconds to an hour; every delivery logged and replayable.

Queue, retry, reconcile

KRA rejections surface immediately with the exact result code; transport failures retry with backoff; nothing signs twice, nothing vanishes.

Device management

OSCU for always-online systems, VSCU for offline-capable tills; serials, keys and invoice sequences handled per branch under your KRA PIN.

Operator console

Every invoice, device and webhook delivery visible; failures retried in one click.

Printable fiscal block

Signature, internal data, CU invoice number, QR payload and verification URL come back per invoice — everything the printed receipt must carry.

Sandbox token and device

A registered device and a working example against the KRA sandbox, within days of writing to us.

Next What a pilot costs → Questions from integration teams →

Want the endpoint reference and a sandbox token?

Write to us with what your system does. We reply with a token, a device and a working example against the KRA sandbox.

hello@ve.ke +254 764 444 408