← Back to blog
·4 min read·SettleRisk Team

gRPC StreamAlerts: Low-Latency Risk Signals for Trading Systems

Deep Dive

Executive Summary

Webhooks deliver events reliably and asynchronously, but the round-trip including DNS, TLS handshake, and HTTP overhead means typical delivery latencies are 200-800ms. For high-frequency MM strategies that re-quote on tier changes, that is too slow. SettleRisk's gRPC StreamAlerts RPC opens a long-lived bidirectional connection over HTTP/2 and pushes events with sub-100ms median latency. This post explains the protocol, walks through a Go consumer, and covers the operational gotchas.

Core Concept

StreamAlerts is a server-streaming RPC defined in the SettleRisk protobuf:

service VelloxService {
  rpc StreamAlerts (StreamAlertsRequest) returns (stream Alert);
}

message StreamAlertsRequest {
  repeated string platforms = 1;   // ["polymarket", "kalshi"]
  repeated string event_types = 2; // ["score.tier_changed", ...]
  string tenant_id = 3;
}

message Alert {
  string event_id = 1;
  string event_type = 2;
  string market_id = 3;
  google.protobuf.Timestamp timestamp = 4;
  google.protobuf.Struct payload = 5;
}

Connect, send a StreamAlertsRequest describing which platforms and event types you care about, and receive Alert messages as the events fire. The connection multiplexes over a single HTTP/2 stream so server push is essentially zero-cost per event.

| Layer | Webhook | gRPC StreamAlerts | |-------|---------|------------------| | Connection | One per event | One per session | | Median latency | 200-800 ms | 30-100 ms | | Plan tier requirement | Builder+ | Fund+ | | Authentication | HMAC per request | mTLS or token, per-session | | Replay | Yes, via /v1/webhook-deliveries | No (use webhooks for replay) |

Worked Example

A Go consumer wired up against the production gRPC endpoint:

package main

import (
    "context"
    "log"
    "time"

    pb "github.com/settlerisk/vellox-proto/v1"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

func main() {
    creds := credentials.NewTLS(nil)
    conn, err := grpc.Dial("api.settlerisk.com:50051", grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("dial: %v", err)
    }
    defer conn.Close()

    client := pb.NewVelloxServiceClient(conn)

    ctx := metadata.AppendToOutgoingContext(
        context.Background(),
        "authorization", "Bearer " + apiKey,
        "x-vellox-key-id", keyID,
    )

    stream, err := client.StreamAlerts(ctx, &pb.StreamAlertsRequest{
        Platforms:  []string{"polymarket", "kalshi"},
        EventTypes: []string{"score.tier_changed", "rules.changed"},
        TenantId:   tenantID,
    })
    if err != nil {
        log.Fatalf("stream: %v", err)
    }

    for {
        alert, err := stream.Recv()
        if err != nil {
            log.Printf("recv error: %v — reconnecting in 2s", err)
            time.Sleep(2 * time.Second)
            return
        }
        log.Printf("[%s] %s -> %s", alert.EventId, alert.MarketId, alert.EventType)
        handleAlert(alert)
    }
}

Python equivalent using grpcio:

import grpc
import vellox_pb2 as pb
import vellox_pb2_grpc

def main():
    creds = grpc.ssl_channel_credentials()
    channel = grpc.secure_channel("api.settlerisk.com:50051", creds)
    stub = vellox_pb2_grpc.VelloxServiceStub(channel)

    metadata = (("authorization", f"Bearer {API_KEY}"), ("x-vellox-key-id", KEY_ID))
    req = pb.StreamAlertsRequest(
        platforms=["polymarket", "kalshi"],
        event_types=["score.tier_changed"],
        tenant_id=TENANT_ID,
    )
    for alert in stub.StreamAlerts(req, metadata=metadata):
        print(f"[{alert.event_id}] {alert.market_id} -> {alert.event_type}")
        handle_alert(alert)

Implementation Notes

Always reconnect on stream error. Long-lived streams will drop. TCP RST, server restarts, network blips — they all happen. Wrap the receive loop with an exponential backoff reconnect.

Use HTTP/2 keepalive. Set grpc.WithKeepaliveParams (Go) or keepalive_time_ms (Python) to 30s so idle streams don't get killed by intermediate proxies.

Don't process inline. Push alerts to a queue and process them in worker goroutines or async tasks. A slow consumer can stall the entire stream.

Subscribe minimally. Each platform + event type filter is server-side. Don't subscribe to * and filter client-side; subscribe only to what you need.

| Concern | Recommendation | |---------|----------------| | Reconnection | Exponential backoff, max 30s delay | | Keepalive | 30s, permit-without-stream | | Backpressure | Async queue, never block stream.Recv | | Auth refresh | Re-resolve token before re-dial | | Tenant isolation | Set tenant_id explicitly |

Failure Modes

1. Blocking the receive loop. If your handler runs synchronously and takes > 1s, you fall behind. Symptoms: rising lag, eventual disconnect.

2. Skipping reconnect logic. First disconnect kills the consumer. Always assume the stream will drop and handle it.

3. Forgetting idempotency. A reconnect may replay a small number of events that were in flight. Dedupe on event_id exactly as you would with webhooks.

4. Wrong proto schema version. The proto file evolves additively but new fields require regenerated stubs. Pin a version in your build.

5. Using StreamAlerts as a replay mechanism. It's not. Use the webhook delivery store for replay. StreamAlerts is for live signal.

Checklist

  • [ ] Exponential backoff reconnect, capped at 30s
  • [ ] HTTP/2 keepalive at 30s
  • [ ] Async handler — never block the receive loop
  • [ ] Dedupe on event_id with 7-day TTL
  • [ ] Subscribe only to platforms and event types you care about
  • [ ] Pin a proto version in your build

Sources + Further Reading

  • SettleRisk docs — gRPC service definition
  • Webhooks post — webhook alternative path
  • gRPC documentation — server-streaming RPCs
  • HTTP/2 keepalive RFC 7540
  • Designing Streaming APIs (Newman 2017)

Fund tier and above include StreamAlerts: /pricing.

Get weekly risk analysis in your inbox

Market risk scores, emerging dispute patterns, and settlement delay trends — delivered every Monday.