SDKs

Which SDKs exist today, which runtimes they cover, and what to do from a language that has none yet.

What exists

languagepackageruntimesstatus
JavaScript / TypeScript@blackevin/clientNode, browser, Bun, Denoavailable
Rubynot yet — what to do meanwhile

One package, two runtimes

Node and the browser are not two SDKs. @blackevin/client is one package that runs in both — same classes, same methods, same wire protocol. There is nothing to choose between and nothing extra to install for one or the other.

What differs is not the API. It is what the code is allowed to hold:

server — Node, Bun, Denobrowser
credentialkey, the API key itselfauthUrl or token. Never the key
endpointBLACKEVIN_ENDPOINT read from the environmentpassed in, because there is no environment at runtime
usual entry pointBlackevin.Rest — publish without holding a socketBlackevin.Realtime — the socket is the point

Both entry points exist in both runtimes; the table is what each is normally for, not a restriction.

From a server

import * as Blackevin from '@blackevin/client';

const rest = new Blackevin.Rest({ key: process.env.BLACKEVIN_KEY });

await rest.channels.get('orders:new').publish('order.created', { id: 7 });

A job, a cron or an inbound webhook publishes over HTTP with no connection to keep open. See the REST overview.

From a browser

import * as Blackevin from '@blackevin/client';

const realtime = new Blackevin.Realtime({
  authUrl: '/api/blackevin-token',
  clientId: String(user.id),
});

const channel = realtime.channels.get('chat:lobby');

channel.subscribe(message => console.log(message.data));

The API key does not appear here, and must not. Your server mints a scoped token and the SDK exchanges it — see Authentication.

The browser has no environment to read, so a web app that needs a non-default endpoint passes its own build's value:

new Blackevin.Realtime({
  endpoint: import.meta.env.VITE_BLACKEVIN_WS,
  authUrl: '/api/blackevin-token',
});

Unset resolves to undefined, which falls through to the default — so the same line works in production without a branch.

ESM and CommonJS

Both. The package publishes an ESM build and a CommonJS one, and the exports map points each at the right files, types included:

import * as Blackevin from '@blackevin/client';
const Blackevin = require('@blackevin/client');

Nothing to configure — your loader picks. TypeScript gets .d.ts on the import side and .d.cts on the require side, so the types resolve either way.

The build targets ES2022, and Node 22 is the floor.

What you get

exportwhat it is
Blackevin.Realtimethe WebSocket client — channels, presence, connection state
Blackevin.Restthe HTTP client — publish, history, presence, token requests
Blackevin.BlackevinErrorthe error type, carrying statusCode and a stable reason slug

Blackevin.Blackevin is an alias of Realtime. Everything exported is public API.

Its only dependency is @blackevin/protocol — the wire format, zero dependencies of its own and browser-safe.

Ruby

There is no Ruby SDK yet. The two things you would reach for one to do, and how to do them today:

Mint a token for a browser. The whole contract is an HMAC-SHA256 over six fields, and Authentication writes it out in full. That recipe was checked against the node's own verifier rather than eyeballed, so it doubles as the specification a Ruby SDK would implement.

Publish from your backend. One HTTP call, no socket:

Net::HTTP.post(
  URI("#{ENV.fetch('BLACKEVIN_REST_URL')}/api/channels/orders%3Anew/publish"),
  { name: "order.created", data: { id: 7 } }.to_json,
  "content-type" => "application/json",
  "authorization" => "Basic #{Base64.strict_encode64(ENV.fetch('BLACKEVIN_ACCESS_KEY'))}"
)

Two things bite here, both covered in REST publish: the channel is a path segment so : must be %3A, and the Basic value is the key verbatim — not user:password.

A language with neither

The wire protocol is documented, not reverse-engineered. A client is a WebSocket, JSON frames, and matching Ack / Nack back to requests by id:

  • Opcodes — the frame shapes, the handshake, the Nack codes
  • JSON codec — serialisation, and what is validated

Both pages are written for someone implementing against them.

On this page