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

  1. Strip CR/LF (and ideally all control characters) from the ID before building the Event.
  2. Use single-line, newline-free identifiers (UUIDs, numeric counters, hex hashes).
  3. 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

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


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)