gofiber/fiber · warning
sse: invalid id
Error message
sse: invalid id: %w
What it means
Thrown by writeEvent when sanitizeField rejects the Event.ID value because it contains a carriage return (\r) or line feed (\n). SSE frames are newline-delimited on the wire, so a CR/LF in the id field would prematurely terminate or corrupt the frame; the library refuses to emit malformed data and returns errInvalidField wrapped as 'sse: invalid id'.
Solutions
- Strip CR/LF (and ideally all control characters) from the ID before building the Event.
- Use single-line, newline-free identifiers (UUIDs, numeric counters, hex hashes).
- If you must round-trip arbitrary data, encode it (base64 without newlines, hex) before using as the ID.
Example fix
// before
stream.Event(sse.Event{ID: multilineInput, Data: payload})
// -> sse: invalid id: field must not contain CR or LF
// after
id := strings.NewReplacer("\r", "", "\n", "").Replace(multilineInput)
stream.Event(sse.Event{ID: id, Data: payload}) Defensive patterns
Strategy: validation
Validate before calling
func safeSSEID(id string) string {
return strings.NewReplacer("\r", "", "\n", "").Replace(id)
}
// then: stream.Event(sse.Event{ID: safeSSEID(rawID), Data: payload}) Type guard
func isValidSSEField(s string) bool {
return !strings.ContainsAny(s, "\r\n")
} Try / catch
id := safeSSEID(rawID)
if err := stream.Event(sse.Event{ID: id, Data: payload}); err != nil {
return // a field-validation or write error ends the stream
} Prevention
- Never echo raw Last-Event-ID or user input into Event.ID without sanitizing.
- Prefer UUIDs / counters / hex hashes as IDs.
- Strip CR, LF (and ideally all C0 controls) from any ID you did not generate.
- Add a unit test that feeds multi-line strings into your event builder.
When it happens
Trigger: Calling stream.Event(...) / writeEvent with an Event whose ID contains a newline or carriage return — e.g. an ID built from multi-line user input, a base64-with-newlines blob, or a copied string that includes a trailing \n.
Common situations: Echoing an untrusted Last-Event-ID header verbatim into a new event.ID; pasting multi-line identifiers; IDs derived from logs or stack traces that contain newlines.
Related errors
- sse: invalid event
- errInvalidField
- basicauth: charset must be UTF-8
- betweenLen constraint requires two arguments
- binder: custom binder not found, please be sure to enter…
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/e963ef62f8117dac.
Report an issue: GitHub.
Appendix: source
Thrown at middleware/sse/event.go:44
// Name sets the SSE event field.
Name string
// Retry sets the SSE retry field for this event.
Retry time.Duration
}
func writeEvent(w *bufio.Writer, event Event, jsonMarshal ...utils.JSONMarshal) error {
data, err := eventData(event.Data, jsonMarshalOrDefault(jsonMarshal))
if err != nil {
return err
}
var frame bytes.Buffer
if event.ID != "" {
id, err := sanitizeField(event.ID)
if err != nil {
return fmt.Errorf("sse: invalid id: %w", err)
}
if id != "" {
appendField(&frame, "id", id)
}
}
if event.Name != "" {
name, err := sanitizeField(event.Name)
if err != nil {
return fmt.Errorf("sse: invalid event: %w", err)
}
if name != "" {
appendField(&frame, "event", name)
}
}
if event.Retry > 0 {
appendField(&frame, "retry", utils.FormatInt(event.Retry.Milliseconds()))
}
if data.hasData {View on GitHub (pinned to a105acad6c)