Streaming & Webhooks
Stream Notes in Real Time
Use Server-Sent Events to display SOAP note sections as they are generated, reducing perceived latency for providers.
Updated March 18, 2026
Why streaming matters
Synchronous note generation takes 10-30 seconds depending on transcript length and specialty complexity. For user-facing applications, that is an eternity of dead spinner. Streaming lets you display the note as it is being generated -- providers see the Subjective section appear within 1-2 seconds, followed by Objective, Assessment, and Plan. This dramatically improves the perceived speed and gives providers confidence that the system is working.
SSE connection setup
The streaming endpoint is POST /v1/stream/note. It accepts the same request body as POST /v1/note but returns a Server-Sent Events (SSE) stream instead of a single JSON response. Set Accept: text/event-stream in your request headers.
cURL
curl -X POST https://api.soapnoteapi.com/v1/stream/note \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"transcript": "Patient is a 52-year-old male presenting with lower back pain for the past 3 weeks. Pain is rated 6/10, worsens with sitting, improves with walking. No radiculopathy. No bowel or bladder changes. Taking ibuprofen 400mg PRN with partial relief.",
"specialty": "primary_care_physician"
}'Event format
The stream emits SSE events. Each event has a data field containing a JSON object with a content string (the next chunk of generated text). The stream ends with a data: [DONE] sentinel.
data: {"content":"Subjective:\n"}
data: {"content":"52-year-old male presents with "}
data: {"content":"lower back pain of three weeks duration. "}
data: {"content":"Pain rated 6/10..."}
...
data: {"content":"\n\nPlan:\n1. Continue ibuprofen..."}
data: [DONE]Browser EventSource example
Note: The native EventSource API only supports GET requests. Since the streaming endpoint is POST, you need to use a library like eventsource-parser or implement manual SSE parsing with fetch. Here is the fetch-based approach:
async function streamNote(transcript, specialty) {
const response = await fetch("https://api.soapnoteapi.com/v1/stream/note", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
body: JSON.stringify({ transcript, specialty }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let noteText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") return noteText;
try {
const parsed = JSON.parse(data);
noteText += parsed.content;
// Update your UI here:
document.getElementById("note-output").textContent = noteText;
} catch {
// Skip malformed lines
}
}
}
}
return noteText;
}Node.js manual parsing
import { createParser } from "eventsource-parser";
const response = await fetch("https://api.soapnoteapi.com/v1/stream/note", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
body: JSON.stringify({
transcript: "Patient presents with lower back pain...",
specialty: "primary_care_physician",
}),
});
let fullNote = "";
const parser = createParser((event) => {
if (event.type === "event") {
if (event.data === "[DONE]") {
console.log("\n--- Stream complete ---");
console.log(fullNote);
return;
}
const { content } = JSON.parse(event.data);
fullNote += content;
process.stdout.write(content);
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
parser.feed(decoder.decode(value, { stream: true }));
}Python SSE client
import requests
import json
response = requests.post(
"https://api.soapnoteapi.com/v1/stream/note",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
json={
"transcript": "Patient presents with lower back pain...",
"specialty": "primary_care_physician",
},
stream=True,
)
full_note = ""
for line in response.iter_lines(decode_unicode=True):
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
try:
parsed = json.loads(data)
full_note += parsed["content"]
print(parsed["content"], end="", flush=True)
except json.JSONDecodeError:
pass
print("\n--- Stream complete ---")
print(full_note)Error handling and reconnection
- If the connection drops mid-stream, you will receive a network error. Retry the full request -- partial results are not resumable.
- A 429 response means your account has insufficient balance. Add funds at your dashboard and try again.
- If you receive an error event in the stream (data: {"error": "..."}), the generation has failed. Display a user-friendly message and let the provider try again.
When to use streaming vs. synchronous
- User-facing applications: Always use streaming. The perceived speed improvement is significant for provider satisfaction.
- Batch processing / backend pipelines: Use the synchronous POST /v1/note endpoint. Simpler to implement and easier to handle errors.
- Audio processing: Audio notes use the polling pattern (PUT /v1/note/audio then poll GET /v1/audio/status/:noteId), not streaming. Streaming is only available for text-to-note generation.
Need help? Contact support@soapnoteapi.com