Oil Price API Documentation - Quick Start in 5 Minutes | REST API
GitHub
GitHub

WebSocket Architecture

This document provides a technical deep-dive into OilPriceAPI's WebSocket infrastructure, designed for developers who need to understand the system internals for optimal integration.

System Overview

OilPriceAPI uses Ruby on Rails ActionCable for WebSocket communication, backed by Redis PubSub for message distribution across multiple server instances.

flowchart LR
    subgraph Clients
        C1[Web App]
        C2[Mobile App]
        C3[Trading System]
    end

    subgraph OilPriceAPI Infrastructure
        LB[Load Balancer]
        subgraph Rails Servers
            R1[Rails + ActionCable]
            R2[Rails + ActionCable]
            R3[Rails + ActionCable]
        end
        REDIS[(Redis PubSub)]
        subgraph Data Pipeline
            PS[Price Scrapers]
            DS[Drilling Sources]
            CACHE[(Price Cache)]
        end
    end

    subgraph External Sources
        EX1[ICE Exchange]
        EX2[CME Group]
        EX3[EIA Data]
        EX4[Baker Hughes]
    end

    C1 & C2 & C3 --> LB
    LB --> R1 & R2 & R3
    R1 & R2 & R3 <--> REDIS
    PS & DS --> CACHE
    CACHE --> REDIS
    EX1 & EX2 & EX3 & EX4 --> PS & DS

Connection Lifecycle

Every WebSocket connection follows this precise sequence:

sequenceDiagram
    participant Client
    participant LoadBalancer
    participant ActionCable
    participant Redis
    participant AuthService

    Client->>LoadBalancer: WSS Handshake Request
    LoadBalancer->>ActionCable: Forward Connection
    ActionCable->>Client: WebSocket Established

    Note over Client,ActionCable: Connection Open

    Client->>ActionCable: Subscribe Command<br/>{channel, api_key}
    ActionCable->>AuthService: Validate API Key
    AuthService-->>ActionCable: User + Plan Details

    alt Invalid Key or Plan
        ActionCable->>Client: Subscription Rejected
    else Valid Reservoir Mastery
        ActionCable->>Redis: SUBSCRIBE energy_prices_{user_id}
        ActionCable->>Client: Subscription Confirmed
        ActionCable->>Client: Welcome Message + Initial Data
    end

    loop Price Updates
        Redis->>ActionCable: New Price Data
        ActionCable->>Client: price_update Message
    end

    loop Heartbeat (every 3s)
        ActionCable->>Client: ping
        Client->>ActionCable: pong
    end

    Note over Client,ActionCable: Connection Maintained

    Client->>ActionCable: Unsubscribe / Close
    ActionCable->>Redis: UNSUBSCRIBE
    ActionCable->>Client: Connection Closed

Connection States

StateDescriptionClient Action
connectingWebSocket handshake in progressWait for open event
openConnection establishedSend subscribe command
subscribedChannel subscription activeProcess incoming messages
disconnectedConnection lostTrigger reconnection logic
rejectedSubscription deniedCheck API key and plan

Channel Architecture

EnergyPricesChannel

The primary channel for all real-time price data.

# Server-side channel implementation (simplified)
class EnergyPricesChannel < ApplicationCable::Channel
  def subscribed
    # Validate Reservoir Mastery subscription
    reject unless current_user&.reservoir_mastery?

    # Subscribe to user-specific stream
    stream_from "energy_prices_#{current_user.id}"

    # Also subscribe to public broadcast stream
    stream_from "energy_prices_public"

    # Send initial price data
    transmit(type: 'welcome', data: current_prices)
  end

  def unsubscribed
    # Cleanup on disconnect
    stop_all_streams
  end
end

Stream Types

StreamPurposeContent
energy_prices_{user_id}Private user streamUser-specific alerts, filtered updates
energy_prices_publicShared broadcastAll commodity price updates

Message Types

flowchart TD
    subgraph Incoming Messages
        M1[welcome]
        M2[price_update]
        M3[drilling_intelligence_update]
        M4[alert]
        M5[ping]
    end

    M1 --> |Initial connection| D1[Initial price snapshot]
    M2 --> |Price change| D2[Commodity price data]
    M3 --> |Weekly/Daily| D3[Rig counts, permits, etc.]
    M4 --> |Threshold breach| D4[Custom alert data]
    M5 --> |Heartbeat| D5[Connection keepalive]

Data Flow Pipeline

Price Update Flow

flowchart LR
    subgraph Data Sources
        ICE[ICE Exchange]
        CME[CME Group]
        TR[Trading Economics]
    end

    subgraph Scraper Workers
        SW1[PriceScraper]
        SW2[FuturesScraper]
    end

    subgraph Processing
        VAL[Validation]
        CACHE[(Redis Cache)]
        DB[(PostgreSQL)]
    end

    subgraph Broadcast
        WS[WebSocket Service]
        REDIS[(Redis PubSub)]
    end

    subgraph Clients
        C1[Client 1]
        C2[Client 2]
        C3[Client N]
    end

    ICE & CME & TR --> SW1 & SW2
    SW1 & SW2 --> VAL
    VAL --> CACHE
    VAL --> DB
    CACHE --> WS
    WS --> REDIS
    REDIS --> C1 & C2 & C3

Update Frequency

Data TypeSourceUpdate FrequencyCache TTL
Spot PricesICE, CMEEvery 30 seconds during market hours30 seconds
FuturesICE, CMEEvery 60 seconds60 seconds
Rig CountsBaker HughesWeekly (Friday 1PM ET)1 hour
Well PermitsState agenciesDaily (varies by state)6 hours
Frac SpreadsPrimary VisionDaily6 hours

Authentication Flow

sequenceDiagram
    participant Client
    participant ActionCable
    participant TokenAuth
    participant UserModel
    participant SubscriptionService

    Client->>ActionCable: Connect with api_key
    ActionCable->>TokenAuth: find_by_token(api_key)
    TokenAuth->>UserModel: Lookup user

    alt Token Invalid
        UserModel-->>ActionCable: nil
        ActionCable->>Client: Connection Rejected
    else Token Valid
        UserModel-->>TokenAuth: User record
        TokenAuth->>SubscriptionService: Check plan
        SubscriptionService-->>TokenAuth: Plan details

        alt Not Reservoir Mastery
            TokenAuth-->>ActionCable: Insufficient plan
            ActionCable->>Client: Subscription Rejected
        else Reservoir Mastery Active
            TokenAuth-->>ActionCable: Authorized
            ActionCable->>Client: Subscription Confirmed
        end
    end

Performance Characteristics

Latency Benchmarks

MetricValueNotes
Connection establishment50-150msIncludes TLS handshake
Subscription confirmation20-50msAfter connection open
Price update delivery30-80msFrom source to client
End-to-end latency100-300msSource change to client receipt

Throughput Limits

ResourceLimitPer
Concurrent connections10API key
Messages receivedUnlimited-
Messages sent (client to server)10/secondConnection
Subscriptions1Connection

Resource Usage

pie title Server Resource Allocation per Connection
    "Memory (per connection)" : 2
    "CPU (idle)" : 1
    "CPU (active message)" : 5
    "Redis subscription" : 1

Per-connection overhead:

  • Memory: ~2KB idle, ~10KB during message processing
  • Redis: 1 SUBSCRIBE per user stream
  • File descriptors: 1 per connection

High Availability Design

Multi-Server Architecture

flowchart TB
    subgraph Region 1
        LB1[Load Balancer]
        R1A[Rails Server A]
        R1B[Rails Server B]
        RED1[(Redis Primary)]
    end

    subgraph Region 2
        LB2[Load Balancer]
        R2A[Rails Server A]
        R2B[Rails Server B]
        RED2[(Redis Replica)]
    end

    GLB[Global Load Balancer]

    GLB --> LB1 & LB2
    LB1 --> R1A & R1B
    LB2 --> R2A & R2B
    R1A & R1B <--> RED1
    R2A & R2B <--> RED2
    RED1 <-.-> |Replication| RED2

Failover Behavior

  1. Server failure: Load balancer routes to healthy servers; clients reconnect automatically
  2. Redis failure: Failover to replica within 30 seconds; brief message gap possible
  3. Region failure: DNS failover to secondary region within 60 seconds

Connection Cleanup

Stale connections are cleaned every 15 minutes:

  • Connections with no ping response in 60 seconds
  • Connections from revoked API keys
  • Connections exceeding per-user limits

Security Model

Transport Security

LayerProtection
TransportTLS 1.3 (minimum 1.2)
Origin validationChecked against allowed origins
Rate limitingConnection attempts limited per IP

Authentication Security

flowchart TD
    REQ[Connection Request]

    REQ --> IP[IP Rate Check]
    IP -->|Exceeded| BLOCK[Block 429]
    IP -->|OK| TOKEN[Token Validation]

    TOKEN -->|Invalid| REJECT1[Reject 401]
    TOKEN -->|Valid| PLAN[Plan Check]

    PLAN -->|Not RM| REJECT2[Reject 403]
    PLAN -->|RM Active| CONN[Connection Check]

    CONN -->|Limit Exceeded| REJECT3[Reject 429]
    CONN -->|Under Limit| ACCEPT[Accept Connection]

Data Protection

  • No sensitive data transmitted over WebSocket
  • API keys used only for authentication, not in message payloads
  • Price data is non-PII and suitable for broadcast

Monitoring & Debugging

Key Metrics

Monitor these metrics for optimal WebSocket performance:

MetricHealthy RangeAlert Threshold
Active connections0-10,000>8,000
Message latency (p95)<100ms>500ms
Connection errors/min<10>50
Redis pub/sub lag<10ms>100ms

Debug Logging

Enable debug logging in your client to troubleshoot issues:

// ActionCable debug mode
ActionCable.logger.enabled = true;

// Native WebSocket verbose logging
const ws = new WebSocket("wss://api.oilpriceapi.com/cable");
ws.onmessage = (e) => console.log("WS Message:", e.data);
ws.onerror = (e) => console.error("WS Error:", e);

Common Issues

SymptomLikely CauseSolution
Connection drops every 60sMissing ping/pongImplement heartbeat handling
No messages after subscribeWrong channel nameVerify EnergyPricesChannel
Subscription rejectedInvalid/expired API keyCheck key and plan status
Intermittent disconnectsNetwork instabilityImplement exponential backoff reconnection

Related Documentation

  • WebSocket API Reference - Implementation examples and message formats
  • Migration Guide - Moving from REST polling to WebSocket
  • Troubleshooting Guide - Common issues and solutions
Last Updated: 2/3/26, 1:27 AM