diff --git a/_layouts/default.html b/_layouts/default.html
index 1b039b0..4c79d0a 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -51,6 +51,18 @@
User Guide
Multi-Perform
+ Server-Sent Events (SSE)
+
+
This project is maintained by Fabian Sauter and Tim Stack
diff --git a/index.md b/index.md
index b54f093..33d64e0 100644
--- a/index.md
+++ b/index.md
@@ -46,6 +46,7 @@ C++ Requests currently supports:
* :cookie: support!
* Proxy support
* Callback interfaces
+* Server-Sent Events (SSE) support
* PUT methods
* DELETE methods
* HEAD methods
diff --git a/sse.md b/sse.md
new file mode 100644
index 0000000..21a7cce
--- /dev/null
+++ b/sse.md
@@ -0,0 +1,322 @@
+---
+layout: default
+title: cpr - Server-Sent Events (SSE)
+---
+
+# Server-Sent Events (SSE) Support
+
+CPR supports Server-Sent Events (SSE), which allows servers to push real-time updates to clients over HTTP. SSE is a standard for unidirectional server-to-client communication over HTTP, commonly used for notifications, live updates, and streaming data.
+
+## Basic Usage
+
+{% raw %}
+```c++
+#include
+#include
+
+int main() {
+ cpr::Session session;
+ session.SetUrl(cpr::Url{"https://example.com/events"});
+
+ // Set up SSE callback
+ session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t userdata) {
+ std::cout << "Event Type: " << event.event << std::endl;
+ std::cout << "Data: " << event.data << std::endl;
+
+ if (event.id.has_value()) {
+ std::cout << "ID: " << event.id.value() << std::endl;
+ }
+
+ if (event.retry.has_value()) {
+ std::cout << "Retry: " << event.retry.value() << " ms" << std::endl;
+ }
+
+ // Return true to continue receiving events, false to stop
+ return true;
+ }
+ }
+ );
+
+ cpr::Response response = session.Get();
+
+ std::cout << "Status: " << response.status_code << std::endl;
+
+ return 0;
+}
+```
+{% endraw %}
+
+## ServerSentEvent Structure
+
+Each SSE event contains the following fields:
+
+{% raw %}
+```c++
+struct ServerSentEvent {
+ std::optional id; // Event ID for tracking/resumption
+ std::string event; // Event type (default: "message")
+ std::string data; // Event data
+ std::optional retry; // Reconnection time in milliseconds
+};
+```
+{% endraw %}
+
+## Using with User Data
+
+You can pass custom user data to the SSE callback:
+
+{% raw %}
+```c++
+#include
+#include
+#include
+
+int main() {
+ std::vector received_events;
+
+ cpr::Session session;
+ session.SetUrl(cpr::Url{"https://example.com/events"});
+
+ // Pass a pointer to user data
+ session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t userdata) {
+ auto* events = reinterpret_cast*>(userdata);
+ events->push_back(event.data);
+ return true;
+ },
+ reinterpret_cast(&received_events)
+ }
+ );
+
+ session.Get();
+
+ std::cout << "Received " << received_events.size() << " events" << std::endl;
+
+ return 0;
+}
+```
+{% endraw %}
+
+## Conditional Event Processing
+
+You can control when to stop receiving events by returning `false` from the callback:
+
+{% raw %}
+```c++
+#include
+#include
+
+int main() {
+ int event_count = 0;
+
+ cpr::Session session;
+ session.SetUrl(cpr::Url{"https://example.com/events"});
+
+ session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t userdata) {
+ int* count = reinterpret_cast(userdata);
+ (*count)++;
+
+ std::cout << "Event #" << *count << ": " << event.data << std::endl;
+
+ // Stop after receiving 10 events
+ return *count < 10;
+ },
+ reinterpret_cast(&event_count)
+ }
+ );
+
+ session.Get();
+
+ std::cout << "Processed " << event_count << " events" << std::endl;
+
+ return 0;
+}
+```
+{% endraw %}
+
+## Using SetOption
+
+SSE callbacks can be set using the `SetOption` method:
+
+{% raw %}
+```c++
+#include
+#include
+
+int main() {
+ cpr::Session session;
+
+ session.SetOption(cpr::Url{"https://example.com/events"});
+ session.SetOption(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t /*userdata*/) {
+ std::cout << event.data << std::endl;
+ return true;
+ }
+ }
+ );
+
+ session.Get();
+
+ return 0;
+}
+```
+{% endraw %}
+
+## Event Types
+
+SSE events can have different types. By default, events have type `"message"`, but servers can specify custom event types:
+
+{% raw %}
+```c++
+session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t /*userdata*/) {
+ if (event.event == "update") {
+ std::cout << "Update: " << event.data << std::endl;
+ } else if (event.event == "notification") {
+ std::cout << "Notification: " << event.data << std::endl;
+ } else {
+ std::cout << "Message: " << event.data << std::endl;
+ }
+ return true;
+ }
+ }
+);
+```
+{% endraw %}
+
+## Event IDs and Retry
+
+SSE events can include additional metadata:
+
+- **id**: A unique identifier for the event, useful for resuming connections after a disconnect
+- **retry**: Server-suggested reconnection time in milliseconds
+
+{% raw %}
+```c++
+std::optional last_event_id;
+
+session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t userdata) {
+ auto* last_id = reinterpret_cast*>(userdata);
+
+ if (event.id.has_value()) {
+ *last_id = event.id.value();
+ }
+
+ std::cout << "Data: " << event.data << std::endl;
+ return true;
+ },
+ reinterpret_cast(&last_event_id)
+ }
+);
+
+session.Get();
+
+// Use last_event_id to resume from last received event if needed
+if (last_event_id.has_value()) {
+ session.SetHeader(cpr::Header{{"Last-Event-ID", last_event_id.value()}});
+ session.Get();
+}
+```
+{% endraw %}
+
+## SSE Format
+
+Server-Sent Events follow this text-based format according to the [HTML5 specification](https://html.spec.whatwg.org/multipage/server-sent-events.html):
+
+```
+event: custom
+id: 123
+retry: 5000
+data: First line of data
+data: Second line of data
+
+```
+
+- Each event is separated by a blank line (`\n\n`)
+- Multiple `data:` fields are concatenated with newlines
+- Lines starting with `:` are comments and are ignored
+- Events without data are ignored
+
+## Important Notes
+
+- **Mutual Exclusivity**: SSE callbacks are mutually exclusive with regular `WriteCallback`. If you set an SSE callback, don't set a write callback and vice versa.
+- **Callback Invocation**: The callback is invoked for each complete SSE event as it's received from the server.
+- **Stopping Reception**: Return `false` from the callback to stop receiving events and close the connection.
+- **Comment Handling**: Comments (lines starting with `:`) are automatically ignored per the SSE specification.
+- **Empty Data**: Events without data fields are ignored per the SSE specification.
+- **Buffering**: The parser correctly handles chunked data, so events can arrive in multiple chunks and will be properly assembled.
+
+## Common Use Cases
+
+### Real-time Notifications
+
+{% raw %}
+```c++
+cpr::Session session;
+session.SetUrl(cpr::Url{"https://api.example.com/notifications"});
+session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t /*userdata*/) {
+ if (event.event == "notification") {
+ std::cout << "New notification: " << event.data << std::endl;
+ }
+ return true; // Keep listening
+ }
+ }
+);
+session.Get();
+```
+{% endraw %}
+
+### Live Data Streams
+
+{% raw %}
+```c++
+cpr::Session session;
+session.SetUrl(cpr::Url{"https://api.example.com/stock-prices"});
+session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t /*userdata*/) {
+ if (event.event == "price-update") {
+ // Parse JSON data and update UI
+ std::cout << "Price update: " << event.data << std::endl;
+ }
+ return true;
+ }
+ }
+);
+session.Get();
+```
+{% endraw %}
+
+### Progress Updates
+
+{% raw %}
+```c++
+cpr::Session session;
+session.SetUrl(cpr::Url{"https://api.example.com/long-running-task/123"});
+session.SetServerSentEventCallback(
+ cpr::ServerSentEventCallback{
+ [](cpr::ServerSentEvent&& event, intptr_t /*userdata*/) {
+ if (event.event == "progress") {
+ std::cout << "Progress: " << event.data << std::endl;
+ } else if (event.event == "complete") {
+ std::cout << "Task completed!" << std::endl;
+ return false; // Stop listening
+ }
+ return true;
+ }
+ }
+);
+session.Get();
+```
+{% endraw %}