Api

WebSocket Activity Stream

Authenticated real-time WebSocket events for droplit activity.

WebSocket Activity Stream

Use the activity stream endpoint to receive real-time droplit events such as taps, pushes, deposits, and sync activity.

Endpoint

GET /faucet/{faucetName}/activity-stream

Replace {faucetName} with your droplit name.

Authentication

The HTTP request upgrades to a WebSocket, then the SDK's Peer performs the BRC-103/104 handshake over that connection. Every protected route uses this mutual-authentication protocol.

Event types

After authentication, the server sends:

  1. connected: ownership is verified and the stream is ready
  2. ping: keep-alive event
  3. faucet_activity: primary activity payload
  4. error: authentication, authorization, or stream failure

Payload shape

type ActivityStreamMessage =
	| { type: "connected"; data: { message: string } }
	| { type: "ping"; data: { time: string } }
	| { type: "faucet_activity"; data: FaucetActivityItem }
	| { type: "error"; status: number; error: string };

Application messages arrive as signed Peer general-message payloads.

Browser transport

The SDK defines the transport interface while Peer owns all signing, nonce, certificate, and session logic:

import {
	Peer,
	type AuthMessage,
	type Transport,
	type WalletInterface,
} from "@bsv/sdk";

class WebSocketTransport implements Transport {
	private pending: Array<{
		message: string;
		resolve: () => void;
	}> = [];

	constructor(private socket: WebSocket) {
		socket.addEventListener("open", () => {
			for (const item of this.pending.splice(0)) {
				socket.send(item.message);
				item.resolve();
			}
		});
	}

	async send(message: AuthMessage): Promise<void> {
		const serialized = JSON.stringify(message);
		if (this.socket.readyState === WebSocket.OPEN) {
			this.socket.send(serialized);
			return;
		}

		await new Promise<void>((resolve) => {
			this.pending.push({ message: serialized, resolve });
		});
	}

	async onData(
		callback: (message: AuthMessage) => Promise<void>,
	): Promise<void> {
		this.socket.addEventListener("message", (event) => {
			void callback(JSON.parse(String(event.data)) as AuthMessage);
		});
	}
}

export async function connectActivityStream(
	apiBaseUrl: string,
	faucetName: string,
	wallet: WalletInterface,
	onEvent: (event: ActivityStreamMessage) => void,
): Promise<WebSocket> {
	const url = new URL(`/faucet/${faucetName}/activity-stream`, apiBaseUrl);
	url.protocol = url.protocol === "https:" ? "wss:" : "ws:";

	const socket = new WebSocket(url);
	const peer = new Peer(wallet, new WebSocketTransport(socket));

	peer.listenForGeneralMessages((_serverIdentityKey, payload) => {
		const json = new TextDecoder().decode(new Uint8Array(payload));
		onEvent(JSON.parse(json) as ActivityStreamMessage);
	});

	await peer.ready;
	await peer.getAuthenticatedSession();
	return socket;
}