The /api/dictate-inject endpoint used to be one-way — it took your words in and returned {ok:true,injected:true} with no reply. Now it holds the HTTP response open until Xen's next reply fires, then returns it:
{ "ok": true, "injected": true, "reply": "<Xen's answer>", "ts": 1781952233207 }
Triggered when the body has "wait": true (or source contains "shortcut"). wait_ms sets the hold timeout — default 90s, max 110s. On timeout it returns reply:"" so the shortcut never hangs. The Xen: identity prefix is stripped so it speaks naturally. VVSVEI & SMS paths are unchanged — zero regression.
https://tui.xlrd.org/api/dictate-injectContent-Type = application/jsonreply in Contents of URLTap the shortcut → speak → it POSTs your words → Xen replies → your phone speaks the answer.
curl -sS -X POST https://tui.xlrd.org/api/dictate-inject \ -H "Content-Type: application/json" \ -d '{"source":"shortcut","text":"what time is it","wait":true,"wait_ms":90000}' # → {"ok":true,"injected":true,"reply":"...Xen's spoken answer...","ts":...}
First reply wins. The held request returns Xen's next reply after your inject — in practice the answer to what you said. If Xen is mid-narration on other work, that line could return instead (acceptable v1; tightenable with a correlation id later).
Timeout. If Xen takes >90s, the shortcut gets reply:"" and stays silent rather than hanging. Raise wait_ms to 110000 max for longer tasks.
Both legs fire. Xen's reply still plays on the Mac speakers AND returns to your phone. Mute the Mac for phone-only — the webhook leg is independent.