Api
SSE Activity Stream
Real-time event streaming for droplit activity.
SSE 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
Requests must include X-Auth-Token generated using the normal signing flow.
Accept: text/event-stream
X-Auth-Token: <signed-token>Important browser note
The native browser EventSource API does not support custom headers.
For authenticated streams, use fetch + ReadableStream parsing (example below).
Event types
faucet_activity: primary activity payloadping: keep-alive event
Payload shape
interface FaucetActivityItem {
id: string;
faucet_name: string;
event_type: string;
timestamp: string;
details: Record<string, unknown>;
metadata?: Record<string, unknown>;
}Example: authenticated stream with fetch
interface FaucetActivityItem {
id: string;
faucet_name: string;
event_type: string;
timestamp: string;
details: Record<string, unknown>;
metadata?: Record<string, unknown>;
}
export async function connectActivityStream(
apiBaseUrl: string,
faucetName: string,
authToken: string,
onMessage: (activity: FaucetActivityItem) => void,
onError: (error: unknown) => void,
signal?: AbortSignal,
) {
const requestPath = `/faucet/${faucetName}/activity-stream`;
const response = await fetch(`${apiBaseUrl}${requestPath}`, {
method: "GET",
headers: {
Accept: "text/event-stream",
"X-Auth-Token": authToken,
},
signal,
});
if (!response.ok) {
throw new Error(`SSE connect failed: ${response.status} ${response.statusText}`);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error("Missing stream reader");
}
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let splitIndex = buffer.indexOf("\n\n");
while (splitIndex !== -1) {
const rawEvent = buffer.slice(0, splitIndex);
buffer = buffer.slice(splitIndex + 2);
if (rawEvent.startsWith("event: faucet_activity")) {
const dataLine = rawEvent
.split("\n")
.find((line) => line.startsWith("data: "));
if (dataLine) {
const json = dataLine.slice("data: ".length);
try {
onMessage(JSON.parse(json) as FaucetActivityItem);
} catch (error) {
onError(error);
}
}
}
splitIndex = buffer.indexOf("\n\n");
}
}
} catch (error) {
if (!signal?.aborted) {
onError(error);
}
}
}React usage sketch
import { useEffect } from "react";
useEffect(() => {
if (!faucetName || !authToken) return;
const abortController = new AbortController();
connectActivityStream(
process.env.NEXT_PUBLIC_DROPLIT_API_URL!,
faucetName,
authToken,
(activity) => {
// Handle event
},
(error) => {
// Handle stream errors
},
abortController.signal,
);
return () => abortController.abort();
}, [faucetName, authToken]);