From a31c91d371ed9a04dd933f17936df8f98c77f54e Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Tue, 21 Apr 2026 15:59:12 +0700 Subject: [PATCH] fix(whatsapp-call): normalize request shape for Go media-server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CreateSessionRequest rejected every Rails call with HTTP 400 — the Go struct expects ice_servers[].urls as []string and account_id as string, but Rails was sending urls as a single string and account_id as an integer. - MediaServerClient#create_session now wraps scalar `urls` in an array and coerces account_id to a string before posting. Defensive normalization lives in the client so existing Call rows (whose meta was persisted in the old shape) keep working without a backfill. - Default ICE server literals in IncomingCallService#default_ice_servers and WhatsappCallsController#create_outbound_call_via_media_server updated to the correct `urls: [..]` shape going forward. Verified end-to-end via rails runner against a locally running media-server: create_session → generate_agent_offer → terminate all return 2xx for both incoming (with Meta SDP offer) and outgoing (server generates the offer for Meta) directions. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/v1/accounts/whatsapp_calls_controller.rb | 2 +- .../services/whatsapp/incoming_call_service.rb | 2 +- .../app/services/whatsapp/media_server_client.rb | 16 +++++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb b/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb index 299daa0b5..a779cda97 100644 --- a/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb @@ -201,7 +201,7 @@ class Api::V1::Accounts::WhatsappCallsController < Api::V1::Accounts::BaseContro call_id: "pending_#{SecureRandom.hex(8)}", direction: 'outgoing', sdp_offer: nil, - ice_servers: [{ urls: 'stun:stun.l.google.com:19302' }], + ice_servers: [{ urls: ['stun:stun.l.google.com:19302'] }], account_id: current_account.id ) diff --git a/enterprise/app/services/whatsapp/incoming_call_service.rb b/enterprise/app/services/whatsapp/incoming_call_service.rb index 859186e28..ee498725c 100644 --- a/enterprise/app/services/whatsapp/incoming_call_service.rb +++ b/enterprise/app/services/whatsapp/incoming_call_service.rb @@ -238,7 +238,7 @@ class Whatsapp::IncomingCallService end def default_ice_servers - [{ urls: 'stun:stun.l.google.com:19302' }] + [{ urls: ['stun:stun.l.google.com:19302'] }] end def fix_sdp_setup(sdp) diff --git a/enterprise/app/services/whatsapp/media_server_client.rb b/enterprise/app/services/whatsapp/media_server_client.rb index afbe6ecd7..8d6af6203 100644 --- a/enterprise/app/services/whatsapp/media_server_client.rb +++ b/enterprise/app/services/whatsapp/media_server_client.rb @@ -5,7 +5,8 @@ class Whatsapp::MediaServerClient TIMEOUT = 10 def create_session(call_id:, direction:, sdp_offer:, ice_servers:, account_id: nil) - body = { call_id: call_id, direction: direction, meta_sdp_offer: sdp_offer, ice_servers: ice_servers, account_id: account_id }.compact + body = { call_id: call_id, direction: direction, meta_sdp_offer: sdp_offer, + ice_servers: normalize_ice_servers(ice_servers), account_id: account_id&.to_s }.compact post('/sessions', body) end @@ -110,4 +111,17 @@ class Whatsapp::MediaServerClient 'Authorization' => "Bearer #{auth_token}" } end + + # Go media server expects `urls` to always be an array of strings. + # Accept legacy data that may have `urls` as a single string. + def normalize_ice_servers(servers) + return [] if servers.blank? + + Array(servers).map do |srv| + s = srv.respond_to?(:to_h) ? srv.to_h.transform_keys(&:to_s) : srv.stringify_keys + urls = s['urls'] + s['urls'] = urls.is_a?(Array) ? urls : Array(urls).compact + s + end + end end