JSON-RPC 2.0 over WebSocket

Realtime API

📘

この接続方法での通信内容は JSON-RPC 2.0 Specification に準拠しています。

エンドポイント

wss://ws.lightstream.bitflyer.com/json-rpc

🚧

  • JSON-RPC 2.0 にて定められた Batch リクエストをサポートしています。リクエストは配列の先頭から順番に処理されます。
  • 接続には TLS 1.2 に対応したクライアントや、場合によっては明示的な設定が必要となります。

サーバーメソッド

  • auth - 認証要求をします
    • params の内容は認証ページを参照してください
    • 認証に成功すると true が返ります (必ず確認してください)
  • subscribe - チャンネルの購読を開始します
    • params: { channel: "(Channel Name)" }
    • 購読が開始されると true が返ります
  • unsubscribe - チャンネルの購読を解除します
    • params: { channel: "(Channel Name)" }
    • 購読が解除されると true が返ります

クライアントメソッド

  • channelMessage - 購読している全チャンネルのメッセージが配信されます

利用例

// Node.js (JavaScript)
const crypto = require("crypto");
const RPCClient = require("jsonrpc2-ws").Client;

const key = "{{ YOUR API KEY }}";
const secret = "{{ YOUR API SECRET }}";

const publicChannels = ["lightning_executions_BTC_JPY"];
const privateChannels = ["child_order_events", "parent_order_events"];

const client = new RPCClient("wss://ws.lightstream.bitflyer.com/json-rpc");

// connection handling
client.on("connected", async () => {
    // subscribe to the Public Channels
    for (const channel of publicChannels) {
        try {
            await client.call("subscribe", { channel });
        } catch (e) {
            console.log(channel, "Subscribe Error:", e);
            continue;
        }
        console.log(channel, "Subscribed.");
    }

    // authentication parameters
    const now = Date.now();
    const nonce = crypto.randomBytes(16).toString("hex");
    const sign = crypto.createHmac("sha256", secret).update(`${now}${nonce}`).digest("hex");

    // request auth
    try {
        await client.call("auth", {
            api_key: key,
            timestamp: now,
            nonce: nonce,
            signature: sign
        });
    } catch (e) {
        console.error("auth", "Authentication Error:", e);
        return;
    }
    console.log("auth", "Authenticated.");

    // subscribe to the Private Channels
    for (const channel of privateChannels) {
        try {
            await client.call("subscribe", { channel });
        } catch (e) {
            console.log(channel, "Subscribe Error:", e);
            continue;
        }
        console.log(channel, "Subscribed.");
    }
});

// channel messages handling
client.methods.set("channelMessage", (client, notify) => {
    console.log("channelMessage", notify.channel, notify.message);
});
// .NET Core / Framework (C# 7.1)
// `dotnet add package StreamJsonRpc -v 1.4.46-beta` or
// `Install-Package -Prerelease StreamJsonRpc`
using Newtonsoft.Json.Linq;
using StreamJsonRpc;
using System;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace LightStreamSampleNetCore
{
    class Program
    {
        static async Task Main(string[] args)
        {
            const string key = "{{ YOUR API KEY }}";
            const string secret = "{{ YOUR API SECRET }}";

            var publicChannels = new[] { "lightning_executions_BTC_JPY" };
            var privateChannels = new[] { "child_order_events", "parent_order_events" };

            using (var ws = new ClientWebSocket())
            {
                await ws.ConnectAsync(new Uri("wss://ws.lightstream.bitflyer.com/json-rpc"), CancellationToken.None);

                using (var rpc = new JsonRpc(new WebSocketMessageHandler(ws)))
                {
                    // channel messages handling
                    rpc.AddLocalRpcMethod("channelMessage", new Action<JToken, CancellationToken>((@params, cancellationToken) =>
                    {
                        var p = @params as dynamic;
                        Console.WriteLine($"{p.channel}: {p.message}");
                    }));

                    rpc.StartListening();

                    // subscribe to the Public Channels
                    foreach (var channel in publicChannels) {
                        await rpc.InvokeWithParameterObjectAsync<object>("subscribe", new { channel });
                        Console.WriteLine($"{channel} Subscribed.");
                    }

                    // authentication parameters
                    var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                    var nonce = Guid.NewGuid().ToString();
                    var sign = SignWithHMACSHA256($"{now}{nonce}", secret);

                    // request auth
                    await rpc.InvokeWithParameterObjectAsync<object>("auth", new {
                        api_key = key,
                        timestamp = now,
                        nonce = nonce,
                        signature = sign,
                    });
                    Console.WriteLine("Authenticated.");

                    // subscribe to the Private Channels
                    foreach (var channel in privateChannels) {
                        await rpc.InvokeWithParameterObjectAsync<object>("subscribe", new { channel });
                        Console.WriteLine($"{channel} Subscribed.");
                    }

                    Console.ReadLine();
                }
            }
        }

        static string SignWithHMACSHA256(string data, string secret)
        {
            using (var encoder = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
            {
                var hash = encoder.ComputeHash(Encoding.UTF8.GetBytes(data));
                return ToHexString(hash);
            }
        }

        static string ToHexString(byte[] bytes)
        {
            var sb = new StringBuilder(bytes.Length * 2);
            foreach (var b in bytes)
            {
                sb.Append(b.ToString("x2"));
            }
            return sb.ToString();
        }
    }
}
require 'rubygems'
require 'websocket-client-simple'
require 'json'
require 'securerandom'

JSONRPC_ID_AUTH = 1
apiKey = "{{ YOUR API KEY }}"
apiSecret = "{{ YOUR API SECRET }}"
publicChannels = ["lightning_board_snapshot_BTC_JPY"]
privateChannels = ["child_order_events"]

# note: reconnection handling needed for production use.
ws = WebSocket::Client::Simple.connect 'wss://ws.lightstream.bitflyer.com/json-rpc'

ws.on :open do
  puts "open"

  publicChannels.each do |channel|
    json = JSON.generate({:method => :subscribe, :params => { :channel => channel }, :id => nil})
    ws.send(json)
  end

  now = Time.now.strftime('%s%L')
  nonce = SecureRandom.hex(16)
  sign = OpenSSL::HMAC.hexdigest("sha256", apiSecret, now + nonce)

  ws.send(JSON.generate({
    :method => :auth,
    :params => {
      :api_key => apiKey,
      :timestamp => now.to_i,
      :nonce => nonce,
      :signature => sign,
    },
    :id => JSONRPC_ID_AUTH
  }))
end

ws.on :message do |msg|
  data = JSON.parse(msg.data)
 
  if data["id"] == JSONRPC_ID_AUTH then
    if data["error"] != nil then
      puts "auth error: " + data["error"]["message"]
      exit
    else
      puts "authed"
      privateChannels.each do |channel|
        json = JSON.generate({:method => :subscribe, :params => { :channel => channel }, :id => nil})
        ws.send(json)
      end
    end
  end

  if data["method"] == "channelMessage" then
    p data["params"]
  end
end

ws.on :close do |e|
  p e
  exit 1
end

ws.on :error do |e|
  p e
end

print "Please press key to exit."
STDIN.getc