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.

Loading GraphiQL...

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.

Loading GraphiQL...

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:

Where each part is switched on

Page

What it does

Administration → Webhooks

Register the URL that receives the events and tick aisBalance under Enabled Events. The same page lists recent successful deliveries and the response body of failed ones, retries an endpoint on demand, and starts one delivering again after it has been given up on.

Bank Accounts → Balance Fetching

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"
                }
            }
        }
    ]
}
aisBalance fields

Field

Description

accountId

Identifies the account the balances belong to. This is the same identifier as in the aisTransactionReport event, so balances and transactions can be joined on it.

provider

Which provider the balances came from. Always DANSKE_BANK.

balances[].amount

The balance in major units, as a decimal string, exactly as the bank reported it. Parse it as a decimal, or use amountMinor instead.

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 amountMinor is in, so that amount == amountMinor / 10^exponent. That relation always holds, and it is the reason to read this field rather than divide by a hardcoded 100. It is Kronor’s scale for the currency and not the ISO 4217 minor unit: today ISK is 0 and every other currency is 2.

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 type collapses.

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 null. This is the field that says what moment the figure describes; triggeredAt says when we fetched it.

accountInfo

Descriptive fields for the account. Any of name, details, iban and bban can be null.

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

Balance types

type

sourceType

Meaning

ITBD

interimBooked

Booked balance of the account itself.

VALU

valueBalance

Value dated balance.

IGBD

intraGroupBooked

Booked position in the group’s cash pool.

IGIN

intraGroupInterest

Intra-group interest position.

OTHR

anything else

A balance type we do not normalise yet. sourceType has the bank’s own name for it.

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.