Treasury¶
In this chapter we will show examples of making a forex trade, and of reading the balances of the bank accounts your organization has authorized.
Schedule Currency Trading¶
Create a new trade by using the scheduleCurrencyTrading mutation. This mutation will create a new trade in the 360t system.
For details of what each input field means, you can check ScheduleCurrencyTradingInput.
The waitToken in the response can be used to check for the status of the trade.
By using the fxForward query, you can get the status and execution details.
Webhook Notifications¶
Kronor will send notifications to the webhook URL provided by the merchant. The notification will be sent when the trade status changes.
The notification will be a POST request with the following payload:
{
"events": [
{
"event": "fxForwardUpdateEvent",
"id": "123456",
"triggeredAt": "2024-11-11T14:42:45.315318+00:00",
"additionalData": [{
"amount": 100000000,
"createdAt": "2024-11-11T14:42:45.315318+00:00",
"currencyToBuy": "EUR",
"currencyToPayWith": "USD",
"desiredQuotes": 4,
"errorMessage": null,
"fxForwardExecutionDetail": {
"executedQuoteId": "2-000004",
"executedRate": "1.05031",
"spotRate": "1.05031",
"forwardPoints": null,
"executionId": "457021057",
"id": 2,
"orderId": "2",
"settleDate": "2024-11-18"
}],
"nominalCurrency": "EUR",
"settlementDate": "2024-11-18",
"status": "success",
"id": 12345
}
}
]
}
Account Balances¶
Once your organization has authorized Kronor to access its bank accounts, we fetch the balances of those accounts and send the result to your organization’s webhook endpoint. Balances come from Danske Bank.
The event is aisBalance: balances were fetched and stored for one account.
Three things have to be true before an account produces it. Danske Bank has to
be the account’s balance provider, balance fetching has to be switched on for
that account, and Danske has to still be listing the account to us. Balance
fetching is off by default and is switched on per account, so authorizing an
account is not by itself enough. An account served by Enable Banking produces
nothing: this event is Danske only, and provider is always
DANSKE_BANK.
On top of that the event itself is disabled by default for your organization, so nothing is sent until you enable it.
Enabling the balance event¶
These are organization webhooks, which are separate from the merchant events in Webhook Notifications: turning on one does not turn on the other, and they are registered in a different place.
That place is the merchant portal, not your backend. The mutation behind it,
setOrganizationWebhookUrl, is not part of the schema an API token sees, so
someone with portal access does this rather than your integration. Two pages set
it up:
Page |
What it does |
|---|---|
Register the URL that receives the events and tick |
|
Tick the authorized accounts whose balances should be fetched. Every account starts unticked, so an endpoint with the event enabled and no account ticked here receives nothing. |
The two pages under Bank Accounts need a portal administrator; Webhooks does not.
Once both are set, Bank Accounts → Accounts can
refresh a single account on demand. That fetches immediately and sends a real
aisBalance, which is the way to check your endpoint without waiting for the
next scheduled run.
The signing secret is shown once, when the endpoint is created. Copy it then: it is not displayed again afterwards, and an endpoint whose secret has been lost has to be replaced with a new one.
Events are enabled per endpoint, so ticking aisBalance on one endpoint does
not affect another, and unticking it is what stops delivery. An organization can
have several endpoints, up to five active ones. Every endpoint with the event
enabled gets its own copy, so two endpoints means two deliveries of the same
event, each signed with its own secret. Deleting an endpoint discards whatever
was still queued for it.
Enabling an event is not retroactive. Only events that happen after you enable it are queued, and there is no backfill of balances fetched before that.
Delivery¶
The endpoint works the same way as it does for merchant webhooks: a JSON
POST, an X-HMAC-SHA256-Signature header covering the exact raw request
body, and a reply of HTTP 200 with [accepted] within 10 seconds. Anything
else counts as a failure and the batch is retried. The retry schedule is the one
in Queued notifications,
and an endpoint that keeps failing for about seven days is switched off and has
to be set up again.
Verify the signature on every request before you act on it. The payload carries account names, IBANs and balances, and an endpoint that does not check the signature will accept a balance from anyone who learns the URL. The worked example in Expose an endpoint on your server applies here as well. Compare the digests with a constant-time comparison, and verify against the bytes as received, before parsing or re-serialising the JSON.
Up to ten events are sent in one request, oldest first, so an event per account
does not mean an HTTP request per account. A batch is not one kind of event: an
aisBalance can arrive in the same request as a settlementReportReady, so
dispatch on each event’s event field rather than on the endpoint it came to.
{
"events": [
{
"event": "aisBalance",
"id": "72316",
"triggeredAt": "2026-09-15 07:30:12.481273+00",
"additionalData": { }
}
]
}
id is a decimal number sent as a string. triggeredAt is a Postgres
timestamp, always UTC, written with a space instead of a T and an offset of
+00, so a strict RFC 3339 parser will reject it. Read it with a format that
accepts a space, or normalise it before parsing.
A batch is accepted or rejected as a whole, so a retry can redeliver events you have already handled.
aisBalance¶
Sent once per account rather than once per balance. Every balance the bank returned for an account arrives in the same event.
{
"events": [
{
"event": "aisBalance",
"id": "72316",
"triggeredAt": "2026-09-15 07:30:12.481273+00",
"additionalData": {
"accountId": 40721,
"provider": "DANSKE_BANK",
"balances": [
{
"type": "ITBD",
"sourceType": "interimBooked",
"amount": "482915.30",
"amountMinor": 48291530,
"exponent": 2,
"currency": "DKK",
"referenceDate": "2026-09-15T04:31:07.412993Z"
},
{
"type": "IGBD",
"sourceType": "intraGroupBooked",
"amount": "73204.88",
"amountMinor": 7320488,
"exponent": 2,
"currency": "DKK",
"referenceDate": "2026-09-15T04:31:07.412993Z"
}
],
"accountInfo": {
"name": "EXAMPLE TRADING A/S",
"details": "Business account",
"iban": "DK8030000012345678",
"bban": "30000012345678"
}
}
}
]
}
Field |
Description |
|---|---|
accountId |
Identifies the account the balances belong to. This is the same identifier
as in the |
provider |
Which provider the balances came from. Always |
balances[].amount |
The balance in major units, as a decimal string, exactly as the bank
reported it. Parse it as a decimal, or use |
balances[].amountMinor |
The same figure as a signed integer in minor units. This is the value Kronor stores, so it will match the figure in a Kronor report exactly. |
balances[].exponent |
The scale |
balances[].currency |
ISO 4217 alpha-3 currency code. |
balances[].type |
Normalised balance type, see Balance types. |
balances[].sourceType |
The bank’s own name for the balance, unnormalised. Useful when you need a
distinction that |
balances[].referenceDate |
When the bank says the balance applies. Passed through as the bank sent
it, so treat the format as the bank’s rather than ours. Can be |
accountInfo |
Descriptive fields for the account. Any of |
Unlike the merchant webhook events, where all money is in minor units,
amount here is in major units. Use amountMinor if you want minor units.
Balances can be negative, and the sign is carried in both amount and
amountMinor. There is no separate debit indicator. Accounts in a cash pool
often hold large negative positions, so a negative balance is normal rather than
an error.
accountInfo carries the account holder’s name and account numbers. Treat the
event as personal data when you decide how long to keep raw payloads and who can
read them.
A balance the bank returns that we cannot convert to an exact integer amount is
left out of balances and recorded on our side instead. The array is
therefore what we could represent, which is not always everything the bank
returned.
Balance types¶
|
|
Meaning |
|---|---|---|
|
|
Booked balance of the account itself. |
|
|
Value dated balance. |
|
|
Booked position in the group’s cash pool. |
|
|
Intra-group interest position. |
|
anything else |
A balance type we do not normalise yet. |
Danske names these four values but does not define them, so the meanings above
are our reading of them and VALU in particular is inferred. sourceType
always carries the bank’s own name, so use that if you need to be sure of what
you are looking at.
Only the types the bank actually returned are present, and which ones those are
differs between accounts. An account in a cash pool usually reports ITBD as
0.00, because the money is swept into the pool and reported under IGBD,
while a standalone account has no IGBD at all. Do not expect a fixed set of
types, and do not treat a missing type as zero.
When nothing is sent¶
A fetch that does not succeed sends nothing, and nothing is sent to say so. An account can go quiet because the bank refused it, because it was closed, or because a run failed, and from your side all of them look the same.
So a missing event carries no information, and an account that stops reporting
will not announce it. If you need to know that a balance has gone stale, compare
referenceDate, or the time you last had an event for that account, against
what you expect.
If no event has ever arrived, check the two switches in
Enabling the balance event first: the account has to be chosen on the
balance fetch accounts page, and aisBalance has to be ticked on the
endpoint.
Ordering events¶
An aisBalance event is sent after every successful fetch, whether or not the
balance moved since the last one, so most events repeat the figures from the
previous one. When you have two events for the same account, order them by
triggeredAt. It is set when we store the balance, in the same transaction,
so it is the time the figure was read rather than the time the request happened
to reach you. Events are queued and sent oldest first as well.
A manual refresh from the merchant portal fetches immediately and sends an
aisBalance the same way, so events do not only arrive on a schedule.
referenceDate answers a different question, which is what moment the figure
describes according to the bank. It is the right thing to show next to a
balance, but not to sort by: it can be null, and it stays the same across
fetches while the balance has not moved.