Skip to main content

Real-time Updates (SSE)

Server-Sent Events (SSE) provide real-time progress updates during report generation. This is the recommended way to monitor report creation status.

Overview​

SSE is a one-way communication channel from server to client, perfect for:

  • Monitoring profile data loading progress
  • Tracking AI summary generation
  • Receiving completion notifications
  • Keeping connections alive with heartbeat pings

Connecting to SSE​

Endpoint​

GET /sse/{reportId}

Authentication​

Bearer token is optional but recommended for authenticated users:

curl -N https://dashboard.socialprofiler.com/api/v1/sse/68af01be012b843fd7bc2bf4 \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Accept: text/event-stream"

Response Headers​

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Event Types​

Ping Events​

The first ping arrives shortly after the connection opens with status: "inited"; subsequent keepalive pings use status: "inprogress" and repeat every 15 seconds by default (server-configured):

event: ping
data: {"type":"ping","status":"inited"}
event: ping
data: {"type":"ping","status":"inprogress"}

Follows Events​

Profile data loading progress:

event: follows
data: {"type":"follows","status":"inprogress","id":"123456789","source":"ig"}
event: follows
data: {"type":"follows","status":"done","id":"123456789","source":"ig"}
event: follows
data: {"type":"follows","status":"error","id":"123456789","source":"ig","error":"private"}

Summary Events​

AI summary generation progress:

event: summary
data: {"type":"summary","status":"inprogress"}
event: summary
data: {"type":"summary","status":"done"}

Confirm Events​

Profile confirmation status:

event: confirm
data: {"type":"confirm","status":"done","id":"123456789","source":"ig"}

Status Values​

StatusDescription
initedTask initialized, waiting to start
inprogressTask is running
doneTask completed successfully
errorTask failed (check error field)

Error Reasons​

When status: "error", the error field contains:

ErrorDescription
privateProfile is private
notFoundProfile doesn't exist
zeroFollowsProfile has no follows
optOutUser opted out of tracking
unloadedData couldn't be loaded
unmappedProfile couldn't be mapped
unexpectedUnexpected failure

Client Implementation​

JavaScript (Browser)​

function connectSSE(reportId, token) {
const url = `https://dashboard.socialprofiler.com/api/v1/sse/${reportId}`;

const eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});

// Handle ping events
eventSource.addEventListener('ping', (event) => {
const data = JSON.parse(event.data);
console.log('Ping:', data.status);
});

// Handle follows loading progress
eventSource.addEventListener('follows', (event) => {
const data = JSON.parse(event.data);
console.log(`Profile ${data.id} (${data.source}): ${data.status}`);

if (data.status === 'error') {
console.error(`Error: ${data.error}`);
}
});

// Handle summary generation
eventSource.addEventListener('summary', (event) => {
const data = JSON.parse(event.data);
console.log(`Summary: ${data.status}`);

if (data.status === 'done') {
console.log('Report ready!');
eventSource.close();
}
});

// Handle errors
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
eventSource.close();
};

return eventSource;
}

// Usage
const sse = connectSSE('68af01be012b843fd7bc2bf4', 'your_token');

// Close when done
// sse.close();

JavaScript (Node.js)​

Using the eventsource package:

const EventSource = require('eventsource');

function connectSSE(reportId, token) {
const url = `https://dashboard.socialprofiler.com/api/v1/sse/${reportId}`;

const eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data);
};

eventSource.onerror = (error) => {
console.error('Error:', error);
};

return eventSource;
}

Python​

Using the sseclient-py package:

import sseclient
import requests

def connect_sse(report_id: str, token: str):
url = f'https://dashboard.socialprofiler.com/api/v1/sse/{report_id}'
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'text/event-stream'
}

response = requests.get(url, headers=headers, stream=True)
client = sseclient.SSEClient(response)

for event in client.events():
data = json.loads(event.data)
event_type = event.event

print(f'{event_type}: {data}')

if event_type == 'summary' and data['status'] == 'done':
print('Report ready!')
break

# Usage
connect_sse('68af01be012b843fd7bc2bf4', 'your_token')

Go​

package main

import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"strings"
)

type SSEMessage struct {
Type string `json:"type"`
Status string `json:"status"`
ID string `json:"id,omitempty"`
Source string `json:"source,omitempty"`
Error string `json:"error,omitempty"`
}

func connectSSE(reportID, token string) error {
url := fmt.Sprintf("https://dashboard.socialprofiler.com/api/v1/sse/%s", reportID)

req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "text/event-stream")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

scanner := bufio.NewScanner(resp.Body)
var eventType string

for scanner.Scan() {
line := scanner.Text()

if strings.HasPrefix(line, "event:") {
eventType = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
} else if strings.HasPrefix(line, "data:") {
data := strings.TrimPrefix(line, "data:")
var msg SSEMessage
json.Unmarshal([]byte(data), &msg)

fmt.Printf("%s: %+v\n", eventType, msg)

if eventType == "summary" && msg.Status == "done" {
fmt.Println("Report ready!")
return nil
}
}
}

return scanner.Err()
}

Best Practices​

1. Handle Reconnection​

SSE connections can drop. Implement automatic reconnection:

function connectWithRetry(reportId, token, maxRetries = 3) {
let retries = 0;

function connect() {
const sse = connectSSE(reportId, token);

sse.onerror = () => {
sse.close();
if (retries < maxRetries) {
retries++;
console.log(`Reconnecting... (attempt ${retries})`);
setTimeout(connect, 1000 * retries);
}
};

return sse;
}

return connect();
}

2. Set Timeouts​

Don't wait forever for events:

const timeout = setTimeout(() => {
sse.close();
console.log('Timeout - checking status via API');
}, 120000); // 2 minutes

sse.addEventListener('summary', (event) => {
if (JSON.parse(event.data).status === 'done') {
clearTimeout(timeout);
}
});

3. Track Progress​

Keep track of which profiles are loaded:

const profileStatus = {};

sse.addEventListener('follows', (event) => {
const data = JSON.parse(event.data);
profileStatus[`${data.source}:${data.id}`] = data.status;

const total = Object.keys(profileStatus).length;
const done = Object.values(profileStatus).filter(s => s === 'done').length;
console.log(`Progress: ${done}/${total}`);
});

4. Graceful Degradation​

Fall back to polling if SSE fails:

async function waitForReport(reportId, token) {
try {
await new Promise((resolve, reject) => {
const sse = connectSSE(reportId, token);
sse.addEventListener('summary', (e) => {
if (JSON.parse(e.data).status === 'done') {
sse.close();
resolve();
}
});
sse.onerror = reject;
});
} catch (error) {
console.log('SSE failed, falling back to polling');
await pollForCompletion(reportId, token);
}
}

async function pollForCompletion(reportId, token) {
while (true) {
const response = await fetch(`/api/v1/check/${reportId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
if (data.generated && data.ready) break;
await new Promise(r => setTimeout(r, 5000));
}
}