Receiving the events
Cash Management publishes every movement of a safe, and every safe audit, as events. An external system subscribes through External Events. Hii Retail services subscribe to the Pub/Sub topics directly.
| External Events source | Event Source ID | Pub/Sub topic | Published when |
|---|---|---|---|
CMM: Cash management transactions (transactions) | cmm.transactions.v2 | cmm.public.event.transactions.v2 | anything moves cash into or out of a safe: one message per movement, see What moves the safe |
CMM: Safe Audits (safe-audits) | cmm.safe-audits.v1 | cmm.public.event.safe-audits.v1 | a safe audit is submitted |
Each source's JSON Schema is linked from External Events. The schemas are also on the event format pages: Transactions and Safe audit.
Which one to take:
- Accounting and ERP:
transactions. Every movement of cash into and out of the safe, with the safe's balance after it. This is the one to book. - Control and follow-up at head office:
safe-audits, to see each store's daily check, its deviation and its explanation.
š Book the transactions, not the safe audits. A safe audit's deviation and bank drop are already published as a
Correction and a BankDeposit transaction. Booking the safe audit's amounts as well counts them twice.
Rules for consuming the eventsā
1. Expect duplicates, and know the keyā
Delivery is at least once, so a message can arrive twice. Skip duplicates by:
| Event | Deduplication key |
|---|---|
transactions | eventId + eventType. Neither is unique on its own: a till audit's InitialBalance and Reconciliation share the till audit's id, and one safe audit's Correction, BankDeposit and customer-defined fields share the safe audit's id. Each line also has its own transactionId. |
safe-audits | safeAuditId. The schema lists it as optional because it was added within version v1, without a new version. A message without it can be deduplicated on repositoryId + eventDateTime. |
2. Do not depend on the arrival orderā
Events can arrive in a different order from the one they were booked in.
eventDateTimeis when the movement happened: the count, the transfer, or the user's action.exportDateTimeis when Cash Management booked it. The balances on a transaction are the safe's balance at that moment, so to keep the latest balance, take it from the transaction with the latestexportDateTime.sequenceNumbernumbers the store's transactions of one event type in the order they were booked.
3. Read the direction from the repository fieldsā
Each line of a transaction names the repository the cash left, the one it entered, or both:
| The line has | Meaning | The safe |
|---|---|---|
repositoryIdTo | cash went into this repository | + amount |
repositoryIdFrom | cash left this repository | ā amount |
amount is positive, except on a BankDeposit or BankWithdrawal that reverses an earlier mistake, which keeps its
minus sign and carries a reasonCode: a BankDeposit of ā500 from the safe puts 500 back into it. The rule above
still gives the right result.
The other end of a movement, a till drawer or the bank, is not a repository. A float or a deposit from a drawer
carries workstationId and, where the store counts per cashier, operatorId.
4. Know which numbers are levels and which are changesā
| Event | Fields | Meaning |
|---|---|---|
transactions | amount | the change to the repository |
transactions | repositoryFromBalance, repositoryToBalance | the repository's level in that currency after the change |
safe-audits | reconciledAmount, cashChangerAmount | the cash from till audits and cash changers in the period the audit covers |
safe-audits | reconciledDeviationAmount, cashChangerDeviationAmount | the till audits' and cash changers' total deviation in that period |
safe-audits | safeAmount | the level counted by hand: the rest of the safe, without that period's till and cash-changer cash |
safe-audits | safeDeviationAmount | safeAmount minus what was expected. Negative means short. Booked as a Correction. |
safe-audits | bankDepositAmount | the bank drop. Booked as a BankDeposit. |
See The safe audit for how the safe-audit amounts fit together.
5. Read the business date, not the publishing timeā
businessDate is the trading day a movement belongs to. A till audit counted after midnight, or recounted the next
morning, still belongs to its own day, and so does the cash it puts into the safe. See
The business date.
6. Read tolerantlyā
- Handle event types you do not recognise. The list grows, and each tenant's customer-defined safe-audit fields add their own. See What moves the safe.
- Ignore fields you do not recognise. Fields are added within a version, and the published schemas do not reject extra fields.
- Fields with no value are left out rather than sent as
null. Only the fields the schema lists as required are always present.
Message attributesā
On Pub/Sub, each message carries these attributes, which you can use for filtering:
| Attribute | Value |
|---|---|
Tenant-Id | the tenant |
Business-Unit-Id | the store |
Correlation-Id | a trace ID for the request that led to the event |
The eventsā
Transactionsā
One message per movement: the store, the event type, who and which till, and one line per currency with the repository and its balance after the movement. Till 1's counted cash from What moves the safe:
{
"tenantId": "tenant-1",
"businessUnitId": "store-12",
"eventId": "tenant-1-store-12-WORKSTATION-CASH_DRAWER-415",
"eventType": "Reconciliation",
"eventDateTime": "2026-09-30T18:05:12.481000000Z",
"correlationId": "f3a6c1d0-5b8e-4a5e-9a7a-2f0d3c9b1e44",
"exportDateTime": "2026-09-30T18:05:13.207114000Z",
"businessDate": "2026-09-30",
"createdBy": "8c1f0d0e-3b6a-4c39-9d0b-5c2e7f1a6b10",
"workstationId": "till-1",
"transactions": [
{
"transactionId": "be7659ea-75d4-42f4-a120-74a51f072b94",
"currencyId": "NOK",
"amount": 1620,
"repositoryIdTo": "e6d2560c-8919-44bb-ab27-ac984364124e",
"repositoryToBalance": 10620,
"sequenceNumber": 1287
}
]
}
Format: Transactions.
The safe auditā
One message per submitted safe audit: who, which safe, and per currency the cash from the tills, the count, the deviation, the bank drop and the customer-defined fields. The audit from The safe audit:
{
"tenantId": "tenant-1",
"businessUnitId": "store-12",
"safeAuditId": "9b1c4e7a-2f3d-4b8e-a6c1-0d5e7f9a3b21",
"repositoryId": "e6d2560c-8919-44bb-ab27-ac984364124e",
"userId": "2a4e6c8b-1d3f-4a5c-8e7b-9c0d1e2f3a4b",
"eventDateTime": "2026-09-30T20:10:44.120000000Z",
"businessDate": "2026-09-30",
"audits": [
{
"currencyId": "NOK",
"reconciledAmount": 1630,
"reconciledDeviationAmount": 0,
"safeAmount": 8995,
"safeDeviationAmount": -5,
"bankDepositAmount": 2625,
"referenceId": "BAG-1042",
"additionalProperties": []
}
]
}
It produces two transactions, both with eventId equal to the safeAuditId:
| eventType | Line |
|---|---|
Correction | NOK 5, repositoryIdFrom the safe, repositoryFromBalance 10 625 |
BankDeposit | NOK 2 625, repositoryIdFrom the safe, repositoryFromBalance 8 000, referenceId BAG-1042 |
Format: Safe audit.