Hi everyone,
I’m implementing a custom backend + ChatKit headless mode using CustomApiConfig.fetch() and NDJSON streaming (as recommended in the advanced ChatKit samples).
The goal is to support a multi-tenant dynamic widget, so ChatKit routes all API calls back to my backend instead of OpenAI’s.
What does work
-
ChatKit component loads properly
-
Custom API layer works
-
Messages are received from backend
-
My NDJSON transformer converts SSE → ChatKit NDJSON
-
ChatKit receives all events (verified in logs)
-
No errors in UI
-
Assistant events fire exactly as expected
What does NOT work
ChatKit displays no assistant message bubble at all.
UI remains empty even though the deltas are arriving.
Logs showing deltas are generated correctly
📤 NDJSON: response.output_text.delta {
"event": "response.output_text.delta",
"data": {
"type": "response.output_text.delta",
"delta": " you",
"response_id": "msg_1763377193210",
"item_id": "item_msg_1763377193210",
"output_index": 0,
"content_index": 0
}
}
📤 NDJSON: response.output_message.delta {
"event": "response.output_message.delta",
"data": {
"id": "msg_1763377193210",
"type": "output_text",
"delta": " you",
"thread_id": "thread_ck_live_7..."
}
}
🔥 DELTA DISPATCHED →
{
event1: 'response.output_text.delta',
event2: 'response.output_message.delta',
item_id: 'item_msg_1763377193210',
message_id: 'msg_1763377193210',
thread_id: 'thread_ck_live_7...',
delta: ' you...'
}
Then:
✅ Message complete event received - will send done events after loop
⏭️ message.complete received, exiting read loop
✅ Sending final completion events and closing stream
📤 NDJSON: response.output_item.done { ... }
📤 NDJSON: response.output_text.done { ... }
📤 NDJSON: response.completed { status: 'completed' }
No errors.
Yet ChatKit renders no message.
NDJSON lifecycle events I’m generating (in order)
Thread + response setup:
response.thread.created (only once)
response.ref
response.started
response.created
response.output_item.added
response.output_message.created
Deltas:
response.output_text.delta
response.output_message.delta
Finalization:
response.output_item.done
response.output_text.done
response.completed
All events match ChatKit’s expected event schema.
Suspected root cause
After digging deeper into ChatKit’s renderer behavior, it looks like:
ChatKit requires a user input bubble created via
response.input_message.created
before it will attach/render the assistant output bubble.
Without an input event, ChatKit seems to drop all deltas silently.
There is no mention of this in the documentation, but the UI logic relies on an internal “input anchor” for the output bubbles.
Hypothesis
Because I’m running in headless mode (CustomApiConfig) and generating the NDJSON manually, ChatKit never receives an input_message.created event, so it does not know where to attach the assistant response.
Questions
-
Is
response.input_message.createdrequired for assistant messages to render when using CustomApiConfig + NDJSON streaming? -
Is there an updated required event sequence for ChatKit v2?
-
Is there a minimal example showing NDJSON → ChatKit mapping with custom backend (headless mode) working?
-
Are both delta events required?
response.output_text.delta response.output_message.delta -
Is there a way to force ChatKit to render output even if no input bubble is present?
Relevant context
This is a multi-tenant widget, so I’m not using the default OpenAI backend.
ChatKit is used purely as the UI renderer, not for threads or tools.
If anyone has a small working example of CustomApiConfig + NDJSON stream with message bubbles successfully rendered, that would be incredibly helpful.
Thank you!