Skip to main content

Overview

PriceSignal provides real-time price updates through GraphQL subscriptions using the WebSocket protocol. This allows your application to receive live market data as prices change, without polling.

WebSocket Endpoint

Connect to the WebSocket endpoint at:
wss://api.pricesignal.com/graphql
The same endpoint (/graphql) is used for both HTTP and WebSocket connections. The server automatically detects the protocol.

Connection Setup

The API uses the graphql-ws protocol (WebSocket subprotocol graphql-transport-ws).

Step 1: Enable WebSockets

The server has WebSockets enabled in Program.cs:159:
app.UseWebSockets();
app.MapGraphQL();

Step 2: Configure In-Memory Subscriptions

PriceSignal uses HotChocolate’s in-memory subscription provider:
.AddSubscriptionType()
.AddInMemorySubscriptions()
This handles subscription lifecycle and message delivery automatically.

Available Subscriptions

onPriceUpdated

Subscribe to real-time price updates for a specific cryptocurrency symbol.
symbol
string
required
The trading pair symbol (e.g., “BTCUSDT”, “ETHUSDT”)
Price
object
Real-time price candle data
symbol
string
Trading pair symbol
open
decimal
Opening price for the candle
high
decimal
Highest price during the candle period
low
decimal
Lowest price during the candle period
close
decimal
Closing/current price
volume
decimal
Trading volume
bucket
datetime
Timestamp of the price candle
exchange
string
Exchange name (e.g., “BINANCE”)

Client Implementation

import { createClient } from 'graphql-ws';
import { getAuth } from 'firebase/auth';

const client = createClient({
  url: 'wss://api.pricesignal.com/graphql',
  connectionParams: async () => {
    const auth = getAuth();
    const token = await auth.currentUser?.getIdToken();
    return {
      Authorization: `Bearer ${token}`
    };
  }
});

// Subscribe to Bitcoin price updates
const unsubscribe = client.subscribe(
  {
    query: `
      subscription OnBitcoinPrice {
        onPriceUpdated(symbol: "BTCUSDT") {
          symbol
          close
          high
          low
          volume
          bucket
        }
      }
    `
  },
  {
    next: (data) => {
      console.log('Price update:', data);
    },
    error: (error) => {
      console.error('Subscription error:', error);
    },
    complete: () => {
      console.log('Subscription completed');
    }
  }
);

// Cleanup when done
// unsubscribe();

Using Apollo Client

import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';
import { getAuth } from 'firebase/auth';

const httpLink = new HttpLink({
  uri: 'https://api.pricesignal.com/graphql'
});

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://api.pricesignal.com/graphql',
    connectionParams: async () => {
      const auth = getAuth();
      const token = await auth.currentUser?.getIdToken();
      return { Authorization: `Bearer ${token}` };
    }
  })
);

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

export const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache()
});

How It Works

The subscription implementation in PriceSignal:
  1. Client subscribes to onPriceUpdated with a specific symbol
  2. Server creates a stream filtered by the requested symbol
  3. Background service (BinancePriceFetcherService) publishes price updates to the event topic
  4. Subscription resolver receives events and filters by symbol
  5. Matching prices are pushed to the client in real-time
From PriceSubscriptions.cs:14-37:
public async IAsyncEnumerable<Price> SubscribeToUpdates(
    [Service] ITopicEventReceiver eventReceiver,
    string symbol,
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    var stream = await eventReceiver.SubscribeAsync<Price>(
        nameof(OnPriceUpdated), cancellationToken);
        
    await foreach (var price in stream.ReadEventsAsync()
        .WithCancellation(cancellationToken))
    {
        if (price.Symbol != symbol) continue;
        yield return price;
    }
}

Multiple Subscriptions

You can subscribe to multiple symbols simultaneously:
const subscriptions = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT'].map(symbol => 
  client.subscribe(
    {
      query: `
        subscription OnPriceUpdated($symbol: String!) {
          onPriceUpdated(symbol: $symbol) {
            symbol
            close
            bucket
          }
        }
      `,
      variables: { symbol }
    },
    {
      next: (data) => console.log(`${symbol} update:`, data)
    }
  )
);

// Cleanup all subscriptions
subscriptions.forEach(unsub => unsub());

Supported Symbols

The API currently supports Binance trading pairs. Common symbols include:
  • BTCUSDT - Bitcoin/USDT
  • ETHUSDT - Ethereum/USDT
Use the GraphQL instruments query to get the full list of available symbols:
query GetInstruments {
  instruments {
    symbol
    name
    exchange
  }
}

Error Handling

Always implement proper error handling for network issues, authentication failures, and subscription termination.

Common Errors

Connection Failed
error
Check your WebSocket URL and network connectivity
Unauthorized
error
Verify your Firebase JWT token is valid and included in connection parameters
Subscription Terminated
error
The server may have restarted or the token expired. Implement reconnection logic.

Reconnection Strategy

import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://api.pricesignal.com/graphql',
  connectionParams: async () => {
    const token = await getAuth().currentUser?.getIdToken();
    return { Authorization: `Bearer ${token}` };
  },
  retryAttempts: 5,
  retryWait: async (retries) => {
    await new Promise(resolve => 
      setTimeout(resolve, Math.min(1000 * 2 ** retries, 30000))
    );
  },
  on: {
    connected: () => console.log('WebSocket connected'),
    closed: () => console.log('WebSocket closed'),
    error: (error) => console.error('WebSocket error:', error)
  }
});

Performance Considerations

  • Limit concurrent subscriptions - Each subscription maintains a server-side stream
  • Unsubscribe when done - Always clean up subscriptions in component unmount
  • Use connection pooling - Reuse the same WebSocket client for multiple subscriptions
  • Monitor memory usage - Long-running subscriptions can accumulate data in client cache

Testing Subscriptions

You can test subscriptions using tools like:
  • GraphQL Playground - Built into the development server
  • Postman - Supports GraphQL subscriptions
  • graphql-ws CLI - Command-line subscription testing

Next Steps

GraphQL API Overview

Learn about queries and mutations

Authentication

Understand how to secure your WebSocket connections

Build docs developers (and LLMs) love