Three issues caused the port to remain bound after partial failures:
1. VoiceCallWebhookServer.start() had no idempotency guard — calling it
while the server was already listening would create a second server on
the same port.
2. createVoiceCallRuntime() did not clean up the webhook server if a step
after webhookServer.start() failed (e.g. manager.initialize). The
server kept the port bound while the runtime promise rejected.
3. ensureRuntime() cached the rejected promise forever, so subsequent
calls would re-throw the same error without ever retrying. Combined
with (2), the port stayed orphaned until gateway restart.
Fixes#32387
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Twilio signs webhook requests using the URL without the port component,
even when the publicUrl config includes a non-standard port. Add a fallback
that strips the port from the verification URL when initial validation fails,
matching the behavior of Twilio's official helper library.
Closes#6334
Address Greptile review: externally-initiated outbound-api calls were
stored with hardcoded direction: "inbound". Now createWebhookCall accepts
a direction parameter so the CallRecord accurately reflects the event's
actual direction. Also skip inboundGreeting for outbound calls and add a
test asserting inbound direction is preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes#30900 — Calls initiated directly via the Twilio REST API
(Direction=outbound-api) were rejected as "unknown call" because
processEvent only auto-registered calls with direction=inbound.
External outbound-api calls now get registered in the CallManager
so the media stream is accepted. Inbound policy checks still only
apply to true inbound calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On gateway restart, persisted non-terminal calls are now verified with
the provider (Twilio/Plivo/Telnyx) before being restored to memory.
This prevents phantom calls from blocking the concurrent call limit.
- Add getCallStatus() to VoiceCallProvider interface
- Implement for all providers with SSRF-guarded fetch
- Transient errors (5xx, network) keep the call with timer fallback
- 404/known-terminal statuses drop the call
- Restart max-duration timers for restored answered calls
- Skip calls older than maxDurationSeconds or without providerCallId