Messages to other services
When a store's rates change, Exchange Rates publishes a message so that the other Hii Retail services can follow.
| Topic | Tells you | Received today by |
|---|---|---|
cer.public.event.rates-changed.v1 | the rates a store uses: new, changed and removed | Store Data |
The topic is for Hii Retail services. It is not offered through External Events: a customer's own system is normally where rates come from, and it can read the current state from the Exchange Rates API.
The topic's name starts with cer, after the Currency & Exchange Rates service that Exchange Rates replaced. The
name and the format are kept on purpose, so that the services already subscribing keep working unchanged.
One message per store
A rate set for a group or for the whole company reaches many stores. Exchange Rates works out which rates each affected store now uses and sends a message for each store whose rates changed. So a subscriber never has to know the company tree.
A message carries the rates that changed for its store, in three lists:
| List | Means, for this store |
|---|---|
createdRates | a rate the store did not have before |
updatedRates | a rate the store already had, with a new value, or the rate with a new markup |
deletedRates | a rate the store no longer has |
A list with nothing in it is left out. Treat a missing list and null the same way.
- Both directions are always there. When a rate is set in one direction only, the message also carries the other direction, worked out as 1 ÷ rate.
- A store with a rate of its own is not affected by a change above it, and gets no message for that change.
- Nothing is sent when nothing changed. The nightly ECB import sends messages only on the nights when a rate actually moves.
The message
{
"tenantId": "your-tenant-id",
"businessUnitId": "stockholm",
"updatedRates": [
{
"exchangeRateType": "SALES",
"sourceCurrencyCode": "SEK",
"targetCurrencyCode": "EUR",
"rateValue": 11.3205,
"rateValueWithMarkup": 11.09409,
"createdDate": "2026-03-02T02:00:04.512Z"
},
{
"exchangeRateType": "SALES",
"sourceCurrencyCode": "EUR",
"targetCurrencyCode": "SEK",
"rateValue": 0.088335,
"rateValueWithMarkup": 0.086568,
"createdDate": "2026-03-02T02:00:04.512Z"
}
]
}
| Field | |
|---|---|
tenantId, businessUnitId | always present. businessUnitId is the store the message is about. |
exchangeRateType | always present: SALES, PURCHASE or INTERCOMPANY |
sourceCurrencyCode | always present: the currency the rate is counted in, the API's currencyIdFrom |
targetCurrencyCode | always present: the currency being priced, the API's currencyIdTo |
rateValue | always present: the rate, up to six decimals |
rateValueWithMarkup | the rate with the store's markup: lowered by that percentage, in both directions, so the store keeps its margin whether a customer pays in the currency or gets money back in it. Equal to rateValue when there is no markup. |
createdDate | always present: when the store first got this rate. It stays the same when the rate changes later. |
createdBy, lastModified | can be present, can be absent |
A rate is identified by the store, exchangeRateType, sourceCurrencyCode and targetCurrencyCode together.
Message attributes:
| Attribute | |
|---|---|
Tenant-Id | the tenant |
Business-Unit-Id | the store the message is about |
Correlation-Id | the id of the change |
Reading a rate in a message
One targetCurrencyCode is worth rateValue of sourceCurrencyCode. sourceCurrencyCode SEK,
targetCurrencyCode EUR, rateValue 11.3205 means 1 EUR = 11.3205 SEK: multiply a euro amount by it to get kronor.
It is the same reading as in the API, see Reading a rate.
🛑 Pick the row by both currencies, never by one. Between SEK and NOK the two directions are 1.0465 and 0.9555. A subscriber that takes the wrong row gets a number that looks reasonable and is about 9 % wrong. Both rows are in every message, so there is never a reason to turn a rate round.
Receiving it correctly
- A message can arrive more than once. Treat
createdRatesandupdatedRatesas this is the store's rate now anddeletedRatesas the store no longer has this rate. Applied that way, a repeated message changes nothing. Key each rate on the store, the type and both currencies. - Messages can arrive in any order. The body has no time of change:
createdDateis when the store first got the rate. If the same rate changes twice in quick succession, use the Pub/Sub publish time to keep the newest, or read the store's rates again fromGET /api/v1/exchange-rates?businessUnit={businessUnitId}. - Filter on the attributes. A subscription can filter on
Tenant-IdorBusiness-Unit-Idto receive only the tenants or stores it serves. - Choose between
rateValueandrateValueWithMarkupdeliberately.rateValueis the rate as it was set or imported, andrateValueWithMarkupis the same rate with the store's markup taken off. Without a markup they are equal, so a subscriber that uses the rate with markup works for stores with and without one. - Ignore fields you do not recognise. The format is kept as it is for existing subscribers, and a reader that tolerates extra fields is safe either way.
Sending the current state again
A Hii Retail service that starts subscribing, or has lost its copy, can ask for the current state to be sent again:
POST https://exchange-rates.retailsvc.com/api/v1/initial-state
{ "tenantId": "your-tenant-id", "businessUnitId": "optional-store-id" }
Leave out businessUnitId to have every store of the tenant sent. The API answers 200 with a correlation id.
Permission: exr.initial-state.create.
Exchange Rates then sends one message per store, listing every rate the store uses, both directions and with
markup, in updatedRates.
⚠ The messages go to every subscriber of the topic, not only to the one that asked. Every subscriber must therefore cope with receiving again what it already has. A subscriber that follows the rules above does.