Subscribe Trade Events
Interface Description
Trade events subscription is a server streaming persistent connection implemented based on gRPC, which is suitable for connecting Webull customers through the OpenAPI development platform. The trade events subscription fully follows the gRPC open source protocol, and you can refer to the gRPC open source library when using it.
Currently, the interface supports order status change message push, and the supported scenarios are as follows:
| scene_type | Description |
|---|---|
| FILLED | Partially filled |
| FINAL_FILLED | All filled |
| PLACE_FAILED | Order failed |
| MODIFY_SUCCESS | Change order successfully |
| MODIFY_FAILED | Change order failed |
| CANCEL_SUCCESS | Cancellation succeeded |
| CANCEL_FAILED | Cancellation failed |
Trade events subscribe Proto protocol definition.
Request Proto
message SubscribeRequest {
uint32 subscribeType = 1; // Subscription type
int64 timestamp = 2; // Timestamp
string contentType = 3; // Content type
string payload = 4; // Content
repeated string accounts = 5; // Account ID
}
Response Proto
message SubscribeResponse {
EventType eventType = 1; // Event type
uint32 subscribeType = 2; // Subscription type
string contentType = 3; // Subscription type
string payload = 4; // Content
string requestId = 5; // Request id
int64 timestamp = 6; // Timestamp
}
EventType enumeration
enum EventType {
SubscribeSuccess = 0; // Subscription succeeded
Ping = 1; // Heartbeat information
AuthError = 2; // Authentication error
NumOfConnExceed = 3; // Connection limit exceeded
SubscribeExpired = 4; // Subscription expired
}
Request Example
- Python
- Java
When using sdk request, subscribeType, timestamp, contentType, and payload can be ignored. Just pass in the accounts. subscribeType currently only supports =1. In the following case, the _on_log method is used to output the log. The my_on_events_message method is to receive order status change messages.
import logging
from webull.trade.events.types import ORDER_STATUS_CHANGED, EVENT_TYPE_ORDER
from webull.trade.trade_events_client import TradeEventsClient
your_app_key = "<your_app_key>"
your_app_secret = "<your_app_secret>"
account_id = "<your_account_id>"
region_id = "<region_id>"
optional_api_endpoint = "<event_api_endpoint>"
def _on_log(level, log_content):
print(logging.getLevelName(level), log_content)
def my_on_events_message(event_type, subscribe_type, payload, raw_message):
if EVENT_TYPE_ORDER == event_type and ORDER_STATUS_CHANGED == subscribe_type:
print('%s' % payload)
if __name__ == '__main__':
# Create EventsClient instance
trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id)
# For non production environment, you need to set the domain name of the subscription service through eventsclient. For example, the domain name of the UAT environment is set here
# trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id, host=optional_api_endpoint)
trade_events_client.on_log = _on_log
# Set the callback function when the event data is received.
# The data of order status change is printed here
trade_events_client.on_events_message = my_on_events_message
# Set the account ID to be subscribed and initiate the subscription. This method is synchronous
trade_events_client.do_subscribe([account_id])
The handleEventMessage method is to receive order status change messages.
import com.google.gson.reflect.TypeToken;
import com.webull.openapi.core.execption.ClientException;
import com.webull.openapi.core.execption.ServerException;
import com.webull.openapi.core.logger.Logger;
import com.webull.openapi.core.logger.LoggerFactory;
import com.webull.openapi.core.serialize.JsonSerializer;
import com.webull.openapi.samples.config.Env;
import com.webull.openapi.trade.events.subscribe.ISubscription;
import com.webull.openapi.trade.events.subscribe.ITradeEventClient;
import com.webull.openapi.trade.events.subscribe.message.EventType;
import com.webull.openapi.trade.events.subscribe.message.SubscribeRequest;
import com.webull.openapi.trade.events.subscribe.message.SubscribeResponse;
import java.util.Map;
public class TradeEventsClient {
private static final Logger logger = LoggerFactory.getLogger(TradeEventsClient.class);
public static void main(String[] args) {
try (ITradeEventClient client = ITradeEventClient.builder()
.appKey(Env.APP_KEY)
.appSecret(Env.APP_SECRET)
.regionId(Env.REGION_ID)
// .host("<event_api_endpoint>")
.onMessage(TradeEventsClient::handleEventMessage)
.build()) {
SubscribeRequest request = new SubscribeRequest("<your_account_id>");
ISubscription subscription = client.subscribe(request);
subscription.blockingAwait();
} catch (ClientException ex) {
logger.error("Client error", ex);
} catch (ServerException ex) {
logger.error("Sever error", ex);
} catch (Exception ex) {
logger.error("Unknown error", ex);
}
}
private static void handleEventMessage(SubscribeResponse response) {
if (SubscribeResponse.CONTENT_TYPE_JSON.equals(response.getContentType())) {
Map<String, String> payload = JsonSerializer.fromJson(response.getPayload(),
new TypeToken<Map<String, String>>(){}.getType());
if (EventType.Order.getCode() == response.getEventType() || EventType.Position.getCode() == response.getEventType()) {
logger.info("{}", payload);
}
}
}
}
Response Example
Transaction event scene type
- FILLED
- FINAL_FILLED
- PLACE_FAILED
- MODIFY_SUCCESS
- CANCEL_SUCCESS
{
"request_id": "1045473299175309312",
"account_id": "4MHSOMIJ88O7E80VBG0O4G6E9A",
"client_order_id": "5783758dc6c240c6811c0cbea60c72d8",
"instrument_id": "913256135",
"order_status": "SUBMITTED",
"symbol": "AAPL",
"qty": "10.00",
"filled_price": "180.00",
"filled_qty": "1.00",
"filled_time": "2025-11-21T06:23:28.601+0000",
"side": "BUY",
"scene_type": "FILLED",
"category": "US_STOCK",
"order_type": "LIMIT"
}
DEBUG response:eventType: Ping
subscribeType: 1
contentType: "text/plain"
requestId: "486daa2d-7be8-438f-b7fa-ecafa3d0e89f"
timestamp: 1763706218682
{
"request_id": "1045474398137483264",
"account_id": "4MHSOMIJ88O7E80VBG0O4G6E9A",
"client_order_id": "db74f19918054a7e9bb72067731c9ae4",
"instrument_id": "913256135",
"order_status": "FILLED",
"symbol": "AAPL",
"qty": "10.00",
"filled_price": "180.00",
"filled_qty": "10.00",
"filled_time": "2025-11-21T06:27:43.312+0000",
"side": "BUY",
"scene_type": "FINAL_FILLED",
"category": "US_STOCK",
"order_type": "LIMIT"
}
DEBUG response:eventType: Ping
subscribeType: 1
contentType: "text/plain"
requestId: "486daa2d-7be8-438f-b7fa-ecafa3d0e89f"
timestamp: 1763706478682
{
"request_id": "1045474643156140032",
"account_id": "4MHSOMIJ88O7E80VBG0O4G6E9A",
"client_order_id": "de2868b71c154bcaafd2baca61127966",
"instrument_id": "913256135",
"order_status": "FAILED",
"symbol": "AAPL",
"qty": "10.00",
"filled_qty": "0.000",
"side": "BUY",
"scene_type": "PLACE_FAILED",
"category": "US_STOCK",
"order_type": "LIMIT"
}
DEBUG response:eventType: Ping
subscribeType: 1
contentType: "text/plain"
requestId: "486daa2d-7be8-438f-b7fa-ecafa3d0e89f"
timestamp: 1763706538682
{
"request_id": "1045475396583161856",
"account_id": "4MHSOMIJ88O7E80VBG0O4G6E9A",
"client_order_id": "92ba046cd87f43c3a93e798877ae1bb8",
"instrument_id": "913256135",
"order_status": "SUBMITTED",
"symbol": "AAPL",
"qty": "10.00",
"filled_qty": "0.000",
"side": "BUY",
"scene_type": "MODIFY_SUCCESS",
"category": "US_STOCK",
"order_type": "LIMIT"
}
DEBUG response:eventType: Ping
subscribeType: 1
contentType: "text/plain"
requestId: "486daa2d-7be8-438f-b7fa-ecafa3d0e89f"
timestamp: 1763706758682
{
"request_id": "1045475396583161856",
"account_id": "4MHSOMIJ88O7E80VBG0O4G6E9A",
"client_order_id": "92ba046cd87f43c3a93e798877ae1bb8",
"instrument_id": "913256135",
"order_status": "CANCELLED",
"symbol": "AAPL",
"qty": "10.00",
"filled_qty": "0.000",
"side": "BUY",
"scene_type": "CANCEL_SUCCESS",
"category": "US_STOCK",
"order_type": "LIMIT"
}
DEBUG response:eventType: Ping
subscribeType: 1
contentType: "text/plain"
requestId: "486daa2d-7be8-438f-b7fa-ecafa3d0e89f"
timestamp: 1763706758682