Skip to main content

External Service Integrations

How InventoryAlert communicates with third-party APIs and external services.


Finnhub API​

  • Purpose: Real-time stock quote data, company metadata, and analytics (earnings, recommendations, insiders, peers)
  • Auth: API key via ?token={API_KEY} query param (from FINNHUB_API_KEY env / appsettings.json → Finnhub:ApiKey)
  • Rate Limits: 60 requests/minute on free tier. Internal cap set at 55 rpm via Redis counter finnhub:ratelimit.

Endpoints Used​

Finnhub EndpointOur RouteCalled By
GET /api/v1/quoteGET /stocks/{symbol}/quoteStockDataService
GET /api/v1/stock/profile2GET /stocks/{symbol}/profileStockDataService
GET /api/v1/stock/market-statusGET /market/statusStockDataService
GET /api/v1/newsGET /market/newsNewsSyncJob (sync) + StockDataService (read)
GET /api/v1/company-newsGET /stocks/{symbol}/newsNewsSyncJob (sync) + StockDataService (read)
GET /api/v1/searchGET /stocks/searchDiscovery flow
GET /api/v1/stock/metricGET /stocks/{symbol}/financialsSyncMetricsJob
GET /api/v1/stock/earningsGET /stocks/{symbol}/earningsSyncEarningsJob
GET /api/v1/stock/recommendationGET /stocks/{symbol}/recommendationSyncRecommendationsJob
GET /api/v1/stock/insider-transactionsGET /stocks/{symbol}/insidersSyncInsidersJob
GET /api/v1/stock/peersGET /stocks/{symbol}/peersStockDataService
GET /api/v1/calendar/earningsGET /market/calendar/earningsStockDataService
GET /api/v1/calendar/ipoGET /market/calendar/ipoStockDataService
GET /api/v1/stock/market-holidayGET /market/holidayStockDataService

Quote Response Mapping​

// Finnhub /quote Response
{
"c": 172.50, // currentPrice
"d": -2.30, // change
"dp": -1.32, // changePercent
"h": 175.00, // high
"l": 170.10, // low
"o": 173.00, // open
"pc": 174.80, // prevClose
"t": 1713000000 // timestamp
}

Error Handling Rules​

  • If c (current price) is null or 0 → skip that symbol, log warning, continue loop.
  • If Finnhub returns an HTTP error → log and skip, never throw.
  • Quotes are cached in Redis for 30 seconds (quote:{symbol}).
// IFinnhubClient contract
if (quote?.CurrentPrice is null or 0) return null; // skip — free tier limitation

Amazon SQS​

  • Purpose: Async event bus between InventoryAlert.Api / InventoryAlert.Worker
  • Local Emulation: Moto running in Docker at http://moto:5000
  • Configuration: Aws:SqsQueueUrl in appsettings.Docker.json

Queues​

QueuePurposePublisherConsumer
inventory-eventsDomain event busApi, WorkerWorker (IntegrationMessageRouter)
inventory-events-dlqDead letter queueSQS (auto-redrive after 5 failures)Manual replay via Hangfire / AWS Console

Consumer Pattern​

// ProcessQueueJob — long-polling
var messages = await _sqsHelper.ReceiveMessagesAsync(queueUrl, maxMessages: 10, ct: ct);
  • Visibility Timeout: 30 seconds (message hidden from other consumers while processing)
  • Max Receive Count: 5 — after 5 failures, message is moved to DLQ automatically
  • Long-polling Wait: 20 seconds per poll cycle to reduce empty-response API calls

Amazon DynamoDB​

  • Purpose: Permanent storage for high-volume news data (never deleted, queryable by range)
  • Local Emulation: Same Moto container as SQS (http://moto:5000)

Tables​

TablePKSKGSI
inventoryalert-market-newsCATEGORY#<category>TS#<unix_ms>—
inventoryalert-company-newsSYMBOL#<ticker>TS#<unix_ms>BySymbolAndDate (Symbol, SK)

Access Patterns​

  • Write: PutItemAsync on news fetch. Deduplication by NewsId attribute before insert.
  • Read (Market News): QueryAsync with PK CATEGORY#general, SK range from TS#{30_days_ago} to TS#{now}.
  • Read (Company News): QueryAsync with PK SYMBOL#TSLA, sorted descending, limit 20.

No TTL: News is retained indefinitely as a historical archive. Use SK range filters to retrieve recent articles.


Redis​

  • Purpose: Quote caching, SQS idempotency markers, alert cooldown
  • Local: localhost:6379 (Docker: inventory-cache)
  • Namespaces:
KeyTTLPurpose
quote:{symbol}30sCached Finnhub quote
msg:processed:{messageId}24hSQS idempotency marker (written after successful processing)
inventoryalert:alerts:cooldown:v1:{userId}:{ruleId}24hAlert cooldown gate (per user + rule)