Streaming FX Quotes

Subscribe to a stream of FX Quotes via a secure WebSocket connection

Use the FX WebSocket to subscribe to streaming FX quotes and execute Market Orders over a secure WebSocket connection.

This integration is intended for more sophisticated FX workflows and is more complex to implement than our REST APIs. We recommend starting with the REST APIs before choosing a WebSocket integration.

Overview

Using the FX WebSocket, you can subscribe to a continuous stream of FX quotes for all EUR- and USD-based currency pairs, plus a small number of additional pairs. These quotes are indicative (non-firm), but you can trade on them using Market Orders subject to symmetric price improvement.

The FX WebSocket uses the same authentication scheme as our REST APIs, so you must obtain an access token through the Authorization API before you connect. Because WebSocket sessions are long-lived, you must also renew the token before it expires to avoid disconnection.

A typical connection lifecycle looks as follows:

  1. Obtain an access token through our Authorization API and establish a WebSocket connection using that token.
  2. Subscribe to pairs such as EUR/USD and GBP/USD by sending a MarketDataRequest.
  3. Banking Circle starts streaming real-time quotes for the subscribed pairs. The stream continues until you unsubscribe, the access token expires, or the WebSocket disconnects.
  4. Send TokenRefresh messages before token expiry to avoid intraday session disconnects.
  5. Execute Market Orders over the WebSocket and optionally specify a target price and slippage. Banking Circle responds with ExecutionReport messages, as it does in our REST APIs.
  6. Unsubscribe from the pairs you no longer need, or disconnect from the WebSocket.

Using Our WebSocket

Once your API user has been configured for streaming FX quotes, you can establish a WebSocket connection in both the Sandbox and Production environments:

To connect, initiate a standard WebSocket handshake. The process starts with an HTTP GET request that is upgraded to a WebSocket connection.

The Handshake Request

Your initial request must include the following headers to comply with the RFC 6455 protocol.

HeaderValueDescription
ConnectionUpgradeSignals the transition from HTTP to another protocol
Hostsandbox.bankingcircleconnect.comSpecifies the server domain
UpgradewebsocketSpecifies the target protocol
Sec-WebSocket-KeyRandom Base64A security key generated by your client
Sec-WebSocket-Version13Specifies the WebSocket protocol version to use
AuthorizationBearer access_tokenYour Banking Circle access token

Replace access_token with your actual token value. Do not include the angle brackets in the header you send.

Messaging

Each inbound and outbound message inherits from BaseMessage, which contains a required type field. Parse this field first to determine how to handle the rest of the payload.

{
	"type" : "<MessageType>"
}

Refreshing Your Token

Authentication happens during the initial handshake. If the Authorization header is missing or the token is invalid, the server returns 401 Unauthorized and the connection is not established.

Token Refresh Logic

Because the WebSocket connection is long-lived, your initial access token may expire while the socket is still open. If you do not renew the token and send a TokenRefresh message before expiry, Banking Circle disconnects your client.

Check your access token TTL (time-to-live) and obtain a new token from the Authorization API before the current token expires.

Once you have renewed your token, you must send a TokenRefresh message:

{
  "type" : "TokenRefresh",
  "bearerToken" : "<access_token>"
}

Subscribing To Market Data

Use MarketDataRequest to subscribe to or unsubscribe from a stream of real-time quotes.

FieldTypeRequiredDescription
typeStringYesMust be set to: MarketDataRequest
customerIdStringYesYour Banking Circle customer id provided to you during onboarding
mdRequestIdStringYesA unique market data request ID identifying this subscription
subscriptionTypeStringYesSet to: Subscribe if you want to receive quotes, or Unsubscribe if you want to stop an existing stream
currencyPairStringYesSet to e.g. EUR/USD or another currency pair
tenorStringYesSet to e.g. SPOT (ON for value Today, and TN for value Tomorrow)
levelIntegerNoOptional. If not specified, 0 is assumed. 0: All available levels. 1: Top of book only.

Send a subscribe request to start streaming quotes for a currency pair:

{
  "type" : "MarketDataRequest",
  "customerId" : "000012345",
  "mdRequestId" : "1aac349acde611ebb8bc0242ac130003",
  "subscriptionType" : "Subscribe", 
  "currencyPair" : "EUR/USD",
  "tenor" : "SPOT", // Also supports ON or TN
  "level" : 0 // Optional 
}

If your MarketDataRequest is not successful, Banking Circle will reject your request with a MarketDataRequestReject message indicating why your subscription was rejected:

{
  "type" : "MarketDataRequestReject",
  "mdReqId" : "1aac349acde611ebb8bc0242ac130003",
  "rejectReason" : "InvalidInput",
  "text": "Customer id is not mapped to Streaming SPOT"
}

If your request is successful, Banking Circle will start streaming quotes using IncrementalRefresh messages:

{
  "type":"IncrementalRefresh",
  "mdReqId" : "1aac349acde611ebb8bc0242ac130003",
  "currencyPair" : "EURUSD",
  "tenor" : "SPOT",
  "size" : 5000000,
  "bid" : 1.1476716,
  "ask" : 1.1598708,
  "timestamp" : "2026-03-12T12:49:37.5129172+00:00"
}

In this example, the quoted size supports trading up to €5 million. If you subscribe to the full book, you receive one IncrementalRefresh message for each available level for that currency pair.

To stop the stream, send another MarketDataRequest with the same mdRequestId and set subscriptionType to Unsubscribe:

{
  "type" : "MarketDataRequest",
  "customerId" : "000012345",
  "mdRequestId" : "1aac349acde611ebb8bc0242ac130003",
  "subscriptionType" : "Unsubscribe", 
  "currencyPair" : "EUR/USD",
  "tenor" : "SPOT"
}

Banking Circle will then stop streaming further quote updates for this currency pair.

Executing Trades

You execute trades by sending Market Orders. Because quotes from the stream are indicative (non-firm), the final execution price depends on the market at the time your order is processed.

The table below summarizes the three supported order styles.

TypeBehaviour
Market OrderExecutes at the current market price. It does not use a target price or slippage limit.
Market Order with target priceExecutes only at your target price or better.
Market Order with slippageExecutes at your target price or better, or within your allowed slippage if the market moves against you.

Example scenario

Assume you have seen EUR/USD at 1.14 / 1.15 on the stream and want to buy EUR. By the time you send the order, the market has moved to 1.13 / 1.16.

Target PriceSlippageResultDescription
FilledFills at 1.16 according to the current market price.
1.17FilledFills at 1.16 because the market has moved in your favour.
1.15RejectedThe market has moved against your target, so the order is rejected.
1.170.01FilledFills at 1.16. Slippage is not used because the market has moved in your favour.
1.150.01FilledFills at 1.16 because the current market price is within your allowed slippage.
1.140.01RejectedRejected because the market is at 1.16 and your target price plus slippage is below that level.

To execute a trade, include the following fields in a MarketOrder message:

Field

Type

Required

Description

type

String

Yes

Must be set to: MarketOrder

clientOrderId

String

Yes

Unique reference on your side identifying this order. Maximum 35 characters. We recommend using UUID without dashes.

customerId

String

Yes

Your Banking Circle customer id provided to you during onboarding

buyCurrency

String

Yes

Specify the currency to be bought e.g. EUR

sellCurrency

String

Yes

Specify the currency to be sold e.g. USD

amountCurrency

String

Yes

Specify whether the order amount refers to the currency being bought or currency being sold.

amount

Decimal

Yes

Specify the order amount. Minimum order amount is 0.01 in EUR equivalent (or 1, if the currency does not support decimals)

tenor

String

Yes

Tenor indicating settlement date for the order.
T+0: ON
T+1: TN
T+2: SPOT

targetPrice

Decimal

No

Optionally specify the target price for this order. Order will be rejected if current market price is against you and no slippage is provided. This field is required if slippage is provided.

maxSlippage

Decimal

No

Optionally specify the maximum slippage (adverse market movement) you are willing to accept. Order will be rejected if current market price is against you even if slippage is taken into consideration

buyAccount

String

No

Optionally specify the bank account to be used for the buy amount

sellAccount

String

No

Optionally specify the bank account to be used for the sell amount

note

String

No

Optionally specify a custom note that is visible in your FX Transactions

Here is an example MarketOrder specifying both target price and slippage:

{
  "type" : "MarketOrder",
  "clientOrderId" : "my-unique-client-order-id", // Use UUID without dashes for uniqueness
  "customerId" : "000012356",
  "buyCurrency" : "EUR",
  "sellCurrency" : "USD",
  "amountCurrency" : "EUR",
  "amount" : 3000000,
  "tenor" : "SPOT",
  "note" : "Q2 Risk Exposure", // Optional
  "buyAccount" : "DK9389000000000214", // Optional - if not provided, default will be used
  "sellAccount" : "DK7789000000000114", // Optional - if not provided, default will be used
  "targetPrice" : 1.1598708, // Optional
  "maxSlippage" : 0.001 // Optional
}

Banking Circle always responds with two execution reports. The first confirms receipt of the order. The second confirms whether the order was filled or rejected.

Below are the fields you can expect in an ExecutionReport:

FieldTypeRequiredDescription
typeStringYesSet to: ExecutionReport
execReportTypeStringYesSet to either "New", "Filled", or "Rejected".
clientOrderIdStringYesEchoes the value sent in your MarketOrder
bankingCircleRefStringYesA unique identifier for this trade on Banking Circle side.
tradeStatusStringYesSet to either: "Pending", "Filled" or "Rejected".
buyCurrencyStringConditional:The currency that was bought. Present only for Filled trades.
buyAmountDecimalConditionalThe amount that was bought. Present only for Filled trades
sellCurrencyStringConditionalThe currency that was sold. Present only for Filled trades
sellAmountDecimalConditionalThe amount that was sold. Present only for Filled trades
currencyPairStringConditionalThe currency pair used, for example EURUSD. Present only for Filled trades
allInRateDecimalConditionalThe exchange rate used for this deal. Present only for Filled trades
settleDateDateConditionalSettlement date for the trade in yyyy-MM-dd format. Present only for Filled trades
tradeExecutionTimeISO 8601 UTCConditionalUTC timestamp for when the trade was executed. Present only for Filled trades
rejectReasonStringConditionalRejection reason for your trade. Only present for Rejected trades

Here is the initial execution report for an accepted order:

{
  "type" : "ExecutionReport",
  "clientOrderId" : "my-unique-client-order-id",
  "bankingCircleRef" : "OX68YAEBCBQ2",
  "tradeStatus" : "Pending", // Indicating the trade is being processed
  "execReportType" : "New" 
}

If the order is filled, Banking Circle then responds with a filled execution report:

{
  "type" : "ExecutionReport",
  "execReportType" : "Filled",
  "clientOrderId" : "my-unique-client-order-id",
  "bankingCircleRef" : "OX68YAEBCBQ2",
  "tradeStatus" : "Filled",
  "buyCurrency" : "EUR",
  "buyAmount" : 3000000,
  "sellCurrency" : "USD",
  "sellAmount" : 3479612.40,
  "currencyPair" : "EURUSD",
  "allInRate" : 1.1598708,
  "settleDate" : "2025-09-24",
  "tradeExecutionTime" : "2025-09-22T08:34:24.1630245+00:00",  
}

If the order is rejected, Banking Circle responds with a rejected execution report instead:

{
  "type" : "ExecutionReport",
  "execReportType" : "Rejected",
  "clientOrderId" : "my-unique-client-order-id",
  "bankingCircleRef" : "OX68YAEBCBQ2",
  "tradeStatus" : "Rejected",
  "rejectReason" : "Outside of our opening hours"
}

All executed orders are visible through our FX Transactions API and follow the FX Transaction Lifecycle, just as they do in our REST APIs.

Error Messages

Banking Circle may send an error message in response to invalid input, validation failures, or unexpected data. The format is as follows:


FieldTypeRequiredDescription
typeStringYesSet to: "ErrorMessage"
errorCodeStringYesMachine-readable error code such as InvalidInput, UnsupportedCurrencyPair.
errorDescriptionStringYesA human-readable explanation of the error

The following example shows the error message returned when you send a message with type WrongMessage.

{
  "type" : "ErrorMessage",
  "errorCode" : "InvalidInput",
  "errorDescription": "Message format is not correct. Make sure you include all fields. Error: Unsupported message type: WrongMessage"
}