fix(twilio): register messaging webhook for voice inboxes and add webhook health tab

This commit is contained in:
Tanmay Deep Sharma
2026-07-14 17:49:26 +05:30
parent 102f19fe41
commit 19ec392f3c
15 changed files with 463 additions and 43 deletions
@@ -36,7 +36,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
end
def setup_webhooks
::Twilio::WebhookSetupService.new(inbox: @inbox).perform
::Twilio::WebhookSetupService.new(channel: @twilio_channel).perform
end
def phone_number
@@ -1,10 +1,10 @@
module Api::V1::Accounts::Concerns::WhatsappHealthManagement
module Api::V1::Accounts::Concerns::InboxHealthManagement
extend ActiveSupport::Concern
included do
skip_before_action :check_authorization, only: [:health, :register_webhook]
before_action :check_admin_authorization?, only: [:register_webhook]
before_action :validate_whatsapp_cloud_channel, only: [:health, :register_webhook]
before_action :validate_health_supported_channel, only: [:health, :register_webhook]
end
def sync_templates
@@ -17,15 +17,14 @@ module Api::V1::Accounts::Concerns::WhatsappHealthManagement
end
def health
health_data = Whatsapp::HealthService.new(@inbox.channel).fetch_health_status
render json: health_data
render json: fetch_health_data
rescue StandardError => e
Rails.logger.error "[INBOX HEALTH] Error fetching health data: #{e.message}"
render json: { error: e.message }, status: :unprocessable_entity
end
def register_webhook
Whatsapp::WebhookSetupService.new(@inbox.channel).register_callback
register_channel_webhook
render json: { message: 'Webhook registered successfully' }, status: :ok
rescue StandardError => e
@@ -35,10 +34,32 @@ module Api::V1::Accounts::Concerns::WhatsappHealthManagement
private
def validate_whatsapp_cloud_channel
return if @inbox.channel.is_a?(Channel::Whatsapp) && @inbox.channel.provider == 'whatsapp_cloud'
def fetch_health_data
return Whatsapp::HealthService.new(@inbox.channel).fetch_health_status if whatsapp_cloud_channel?
render json: { error: 'Health data only available for WhatsApp Cloud API channels' }, status: :bad_request
Twilio::HealthService.new(channel: @inbox.channel).perform
end
def register_channel_webhook
return Whatsapp::WebhookSetupService.new(@inbox.channel).register_callback if whatsapp_cloud_channel?
Twilio::WebhookSetupService.new(channel: @inbox.channel).perform
# No-op unless voice is enabled; keeps the number's voice webhooks in sync alongside messaging.
@inbox.channel.try(:reprovision_voice_webhooks!)
end
def validate_health_supported_channel
return if whatsapp_cloud_channel? || twilio_sms_channel?
render json: { error: 'Health data only available for WhatsApp Cloud API and Twilio SMS channels' }, status: :bad_request
end
def whatsapp_cloud_channel?
@inbox.channel.is_a?(Channel::Whatsapp) && @inbox.channel.provider == 'whatsapp_cloud'
end
def twilio_sms_channel?
@inbox.channel.is_a?(Channel::TwilioSms) && @inbox.channel.sms?
end
def whatsapp_channel?
@@ -5,7 +5,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
# we are already handling the authorization in fetch inbox
before_action :check_authorization, except: [:show]
include Api::V1::Accounts::Concerns::WhatsappHealthManagement
include Api::V1::Accounts::Concerns::InboxHealthManagement
def index
@inboxes = policy_scope(Current.account.inboxes)
@@ -687,6 +687,25 @@
},
"CHANNEL_PREFERENCES": "Channel Preferences",
"WIDGET_FEATURES": "Widget features",
"TWILIO_HEALTH": {
"TITLE": "Webhook configuration",
"DESCRIPTION": "Twilio needs these webhooks configured on your number for Chatwoot to receive messages and calls",
"NO_DATA": "Health data is not available",
"WEBHOOKS": {
"MESSAGING": "Messaging webhook",
"VOICE": "Voice webhook",
"VOICE_STATUS": "Voice status callback",
"VOICE_APP": "Outbound calling (TwiML app)"
},
"WEBHOOK": {
"CONFIGURED_SUCCESS": "Webhook configured successfully",
"ACTION_REQUIRED": "Webhook not configured",
"URL_MISMATCH": "Webhook URL mismatch",
"REGISTER_BUTTON": "Register Webhook",
"NOT_SET": "not set",
"TOOLTIP": "Expected {expected}, currently set to {actual}"
}
},
"ACCOUNT_HEALTH": {
"TITLE": "Manage your WhatsApp account",
"DESCRIPTION": "Review your WhatsApp account status, messaging limits, and quality. Update settings or resolve issues if needed",
@@ -29,6 +29,7 @@ import CustomerSatisfactionPage from './settingsPage/CustomerSatisfactionPage.vu
import CollaboratorsPage from './settingsPage/CollaboratorsPage.vue';
import BotConfiguration from './components/BotConfiguration.vue';
import AccountHealth from './components/AccountHealth.vue';
import TwilioHealth from './components/TwilioHealth.vue';
import WhatsappManualMigrationDialog from './components/WhatsappManualMigrationDialog.vue';
import WhatsappManualMigrationBanner from './components/WhatsappManualMigrationBanner.vue';
import { FEATURE_FLAGS } from '../../../../featureFlags';
@@ -80,6 +81,7 @@ export default {
ColorPicker,
SelectInput,
AccountHealth,
TwilioHealth,
WhatsappManualMigrationDialog,
WhatsappManualMigrationBanner,
Widget,
@@ -159,6 +161,9 @@ export default {
shouldShowWhatsAppConfiguration() {
return this.isAWhatsAppCloudChannel;
},
shouldShowTwilioHealth() {
return this.isATwilioChannel && this.inbox.medium === 'sms';
},
whatsAppAPIProviderName() {
if (this.isAWhatsAppCloudChannel) {
return this.$t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD');
@@ -243,6 +248,16 @@ export default {
];
}
if (this.shouldShowTwilioHealth) {
visibleToAllChannelTabs = [
...visibleToAllChannelTabs,
{
key: 'twilio-health',
name: this.$t('INBOX_MGMT.TABS.ACCOUNT_HEALTH'),
},
];
}
if (
this.isATwilioChannel &&
this.inbox.phone_number &&
@@ -572,7 +587,7 @@ export default {
async fetchHealthData() {
if (!this.inbox) return;
if (!this.isAWhatsAppCloudChannel) {
if (!this.isAWhatsAppCloudChannel && !this.shouldShowTwilioHealth) {
return;
}
@@ -1408,6 +1423,13 @@ export default {
@register-webhook="registerWebhook"
/>
</div>
<div v-if="selectedTabKey === 'twilio-health'">
<TwilioHealth
:health-data="healthData"
:is-registering-webhook="isRegisteringWebhook"
@register-webhook="registerWebhook"
/>
</div>
<WhatsappManualMigrationDialog
v-if="showWhatsAppManualMigration"
ref="whatsappManualMigrationDialog"
@@ -0,0 +1,123 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import ButtonV4 from 'next/button/Button.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({
healthData: {
type: Object,
default: null,
},
isRegisteringWebhook: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['registerWebhook']);
const { t } = useI18n();
const WEBHOOK_LABELS = {
messaging: 'INBOX_MGMT.TWILIO_HEALTH.WEBHOOKS.MESSAGING',
voice: 'INBOX_MGMT.TWILIO_HEALTH.WEBHOOKS.VOICE',
voice_status: 'INBOX_MGMT.TWILIO_HEALTH.WEBHOOKS.VOICE_STATUS',
voice_app: 'INBOX_MGMT.TWILIO_HEALTH.WEBHOOKS.VOICE_APP',
};
const webhooks = computed(() =>
(props.healthData?.webhooks || []).map(webhook => ({
...webhook,
label: t(WEBHOOK_LABELS[webhook.name]),
tooltip: t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.TOOLTIP', {
expected: webhook.expected,
actual: webhook.actual || t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.NOT_SET'),
}),
}))
);
const handleRegisterWebhook = () => emit('registerWebhook');
</script>
<template>
<div class="gap-4 mx-6">
<div
class="px-5 py-5 space-y-6 rounded-xl outline outline-1 -outline-offset-1 outline-n-weak bg-n-solid-2"
>
<div>
<span class="text-heading-3 text-n-slate-12">
{{ t('INBOX_MGMT.TWILIO_HEALTH.TITLE') }}
</span>
<p class="mt-1 text-body-main text-n-slate-11">
{{ t('INBOX_MGMT.TWILIO_HEALTH.DESCRIPTION') }}
</p>
</div>
<div v-if="healthData" class="grid grid-cols-1 gap-4 xs:grid-cols-2">
<div
v-for="webhook in webhooks"
:key="webhook.name"
class="flex flex-col gap-2 p-4 rounded-lg border border-n-weak bg-n-solid-1"
>
<div class="flex gap-2 items-center">
<span class="text-body-main font-medium text-n-slate-11">
{{ webhook.label }}
</span>
<Icon
v-tooltip.top="webhook.tooltip"
icon="i-lucide-info"
class="flex-shrink-0 w-4 h-4 cursor-help text-n-slate-9"
/>
</div>
<div class="flex gap-3 justify-between items-center">
<span
v-if="webhook.configured"
class="inline-flex items-center gap-1.5 px-2 py-0.5 min-h-6 text-label-small rounded-md bg-n-alpha-2 text-n-teal-11"
>
<Icon icon="i-lucide-check-circle" class="w-3.5 h-3.5" />
{{ t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.CONFIGURED_SUCCESS') }}
</span>
<span
v-else
class="inline-flex items-center gap-1.5 px-2 py-0.5 min-h-6 text-label-small rounded-md bg-n-alpha-2 text-n-amber-11"
>
<Icon icon="i-lucide-alert-triangle" class="w-3.5 h-3.5" />
{{
webhook.actual
? t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.URL_MISMATCH')
: t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.ACTION_REQUIRED')
}}
</span>
<ButtonV4
v-if="!webhook.configured"
sm
solid
blue
:loading="isRegisteringWebhook"
:disabled="isRegisteringWebhook"
class="flex-shrink-0"
@click="handleRegisterWebhook"
>
{{ t('INBOX_MGMT.TWILIO_HEALTH.WEBHOOK.REGISTER_BUTTON') }}
</ButtonV4>
</div>
</div>
</div>
<div v-else class="pt-8">
<div
class="flex justify-center items-center p-8 text-center text-n-slate-11"
>
<div>
<Icon icon="i-lucide-activity" class="mb-2 w-8 h-8" />
<p class="text-body-main text-n-slate-11">
{{ t('INBOX_MGMT.TWILIO_HEALTH.NO_DATA') }}
</p>
</div>
</div>
</div>
</div>
</div>
</template>
+52
View File
@@ -0,0 +1,52 @@
class Twilio::HealthService
include Rails.application.routes.url_helpers
pattr_initialize [:channel!]
# Compares the webhooks Twilio actually has against the ones Chatwoot expects.
# Errors (bad credentials, unknown number) bubble up to the controller as a 422.
def perform
webhooks = channel.messaging_service_sid? ? messaging_service_webhooks : phone_number_webhooks
{
status: webhooks.all? { |webhook| webhook[:configured] } ? 'healthy' : 'misconfigured',
webhooks: webhooks
}
end
private
def messaging_service_webhooks
service = channel.client.messaging.services(channel.messaging_service_sid).fetch
[webhook('messaging', twilio_callback_index_url, service.inbound_request_url)]
end
def phone_number_webhooks
number = channel.client.incoming_phone_numbers.list(phone_number: channel.phone_number).first
raise "Phone number #{channel.phone_number} was not found in the connected Twilio account" if number.nil?
webhooks = [webhook('messaging', twilio_callback_index_url, number.sms_url)]
webhooks += voice_webhooks(number) if channel.voice_enabled?
webhooks
end
def voice_webhooks(number)
[
webhook('voice', channel.voice_call_webhook_url, number.voice_url),
webhook('voice_status', channel.voice_status_webhook_url, number.status_callback),
# Outbound calls dial through the TwiML app, so a stale voice_url here breaks them silently.
webhook('voice_app', channel.voice_call_webhook_url, twiml_app_voice_url)
]
end
def twiml_app_voice_url
return if channel.twiml_app_sid.blank?
channel.client.applications(channel.twiml_app_sid).fetch.voice_url
end
def webhook(name, expected, actual)
{ name: name, expected: expected, actual: actual.presence, configured: expected == actual }
end
end
+1 -5
View File
@@ -1,7 +1,7 @@
class Twilio::WebhookSetupService
include Rails.application.routes.url_helpers
pattr_initialize [:inbox!]
pattr_initialize [:channel!]
def perform
if channel.messaging_service_sid?
@@ -41,10 +41,6 @@ class Twilio::WebhookSetupService
@phone_numbers ||= twilio_client.incoming_phone_numbers.list(phone_number: channel.phone_number)
end
def channel
@channel ||= inbox.channel
end
def twilio_client
@twilio_client ||= ::Twilio::REST::Client.new(channel.account_sid, channel.auth_token)
end
@@ -100,7 +100,7 @@ module Enterprise::Api::V1::Accounts::InboxesController
)
config = voice_params[:provider_config] || {}
Current.account.twilio_sms.create!(
channel = Current.account.twilio_sms.create!(
phone_number: voice_params[:phone_number],
account_sid: config[:account_sid],
auth_token: config[:auth_token],
@@ -109,5 +109,10 @@ module Enterprise::Api::V1::Accounts::InboxesController
medium: :sms,
voice_enabled: true
)
# A voice channel is an SMS channel with voice_enabled, so it needs the messaging
# webhook too. Voice webhooks are provisioned by the model's provision_twiml_app hook.
::Twilio::WebhookSetupService.new(channel: channel).perform
channel
end
end
@@ -20,6 +20,15 @@ module Enterprise::Channel::TwilioSms
)
end
# Re-points the TwiML app and the number at our voice webhooks, reusing the existing app.
def reprovision_voice_webhooks!
return unless voice_enabled?
service = ::Twilio::VoiceWebhookSetupService.new(channel: self)
update!(twiml_app_sid: service.sync_twiml_app!)
service.configure_number_webhooks!
end
def voice_call_webhook_url
digits = phone_number.delete_prefix('+')
Rails.application.routes.url_helpers.twilio_voice_call_url(phone: digits)
@@ -14,6 +14,40 @@ class Twilio::VoiceWebhookSetupService
app_sid
end
# Outbound calls dial through the TwiML app, so its voice_url must track the current host too.
def sync_twiml_app!
return create_twiml_app! if channel.twiml_app_sid.blank?
channel.client.applications(channel.twiml_app_sid).update(
voice_url: channel.voice_call_webhook_url,
voice_method: HTTP_METHOD
)
channel.twiml_app_sid
rescue StandardError => e
log_twilio_error('TWIML_APP_UPDATE', e)
raise
end
def configure_number_webhooks!
numbers = channel.client.incoming_phone_numbers.list(phone_number: channel.phone_number)
if numbers.empty?
Rails.logger.warn "TWILIO_PHONE_NUMBER_NOT_FOUND: #{channel.phone_number}"
return
end
channel.client
.incoming_phone_numbers(numbers.first.sid)
.update(
voice_url: channel.voice_call_webhook_url,
voice_method: HTTP_METHOD,
status_callback: channel.voice_status_webhook_url,
status_callback_method: HTTP_METHOD
)
rescue StandardError => e
log_twilio_error('NUMBER_WEBHOOKS_UPDATE', e)
raise
end
private
def validate_token_credentials!
@@ -36,26 +70,6 @@ class Twilio::VoiceWebhookSetupService
raise
end
def configure_number_webhooks!
numbers = channel.client.incoming_phone_numbers.list(phone_number: channel.phone_number)
if numbers.empty?
Rails.logger.warn "TWILIO_PHONE_NUMBER_NOT_FOUND: #{channel.phone_number}"
return
end
channel.client
.incoming_phone_numbers(numbers.first.sid)
.update(
voice_url: channel.voice_call_webhook_url,
voice_method: HTTP_METHOD,
status_callback: channel.voice_status_webhook_url,
status_callback_method: HTTP_METHOD
)
rescue StandardError => e
log_twilio_error('NUMBER_WEBHOOKS_UPDATE', e)
raise
end
def log_twilio_error(context, error)
details = build_error_details(context, error)
add_twilio_specific_details(details, error)
@@ -1233,7 +1233,7 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response).to have_http_status(:bad_request)
json_response = response.parsed_body
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API channels')
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API and Twilio SMS channels')
end
it 'returns bad request error for agent' do
@@ -1245,7 +1245,7 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response).to have_http_status(:bad_request)
json_response = response.parsed_body
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API channels')
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API and Twilio SMS channels')
end
end
@@ -1262,7 +1262,7 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response).to have_http_status(:bad_request)
json_response = response.parsed_body
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API channels')
expect(json_response['error']).to eq('Health data only available for WhatsApp Cloud API and Twilio SMS channels')
end
end
@@ -1277,4 +1277,49 @@ RSpec.describe 'Inboxes API', type: :request do
end
end
end
describe 'Twilio inbox health' do
let(:twilio_channel) { create(:channel_twilio_sms, :with_phone_number, account: account) }
let(:twilio_inbox) { create(:inbox, account: account, channel: twilio_channel) }
let(:health_service) { instance_double(Twilio::HealthService) }
let(:health_data) do
{ status: 'misconfigured', webhooks: [{ name: 'messaging', configured: false }] }
end
let(:webhook_service) { instance_double(Twilio::WebhookSetupService, perform: true) }
before do
allow(Twilio::HealthService).to receive(:new).with(channel: twilio_channel).and_return(health_service)
allow(health_service).to receive(:perform).and_return(health_data)
allow(Twilio::WebhookSetupService).to receive(:new).with(channel: twilio_channel).and_return(webhook_service)
end
it 'returns the twilio webhook health' do
get "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}/health",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(response.parsed_body['status']).to eq('misconfigured')
end
it 'returns bad request for a twilio whatsapp inbox' do
whatsapp_medium_inbox = create(:inbox, account: account, channel: create(:channel_twilio_sms, :whatsapp, account: account))
get "/api/v1/accounts/#{account.id}/inboxes/#{whatsapp_medium_inbox.id}/health",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:bad_request)
end
it 'registers the messaging webhook' do
post "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}/register_webhook",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(webhook_service).to have_received(:perform)
end
end
end
@@ -31,6 +31,8 @@ RSpec.describe 'Enterprise Inboxes API', type: :request do
headers: { 'Content-Type' => 'application/json' })
allow(Twilio::VoiceWebhookSetupService).to receive(:new).and_return(instance_double(Twilio::VoiceWebhookSetupService,
perform: "AP#{SecureRandom.hex(16)}"))
messaging_webhook_service = instance_double(Twilio::WebhookSetupService, perform: true)
allow(Twilio::WebhookSetupService).to receive(:new).and_return(messaging_webhook_service)
post "/api/v1/accounts/#{account.id}/inboxes",
headers: admin.create_new_auth_token,
@@ -45,6 +47,8 @@ RSpec.describe 'Enterprise Inboxes API', type: :request do
expect(response).to have_http_status(:success)
expect(response.body).to include('Voice Inbox')
expect(response.body).to include('+15551234567')
# the number must also receive SMS, not just calls
expect(messaging_webhook_service).to have_received(:perform)
end
end
end
+110
View File
@@ -0,0 +1,110 @@
require 'rails_helper'
NUMBER_INSTANCE = Twilio::REST::Api::V2010::AccountContext::IncomingPhoneNumberInstance
NUMBER_LIST = Twilio::REST::Api::V2010::AccountContext::IncomingPhoneNumberList
describe Twilio::HealthService do
include Rails.application.routes.url_helpers
let(:twilio_client) { instance_double(Twilio::REST::Client) }
let(:numbers_list) { instance_double(NUMBER_LIST) }
before do
allow(Twilio::REST::Client).to receive(:new).and_return(twilio_client)
allow(twilio_client).to receive(:incoming_phone_numbers).and_return(numbers_list)
end
describe '#perform' do
context 'with a phone number' do
let(:channel) { create(:channel_twilio_sms, :with_phone_number) }
let(:sms_url) { twilio_callback_index_url }
before do
allow(numbers_list).to receive(:list).and_return([instance_double(NUMBER_INSTANCE, sms_url: sms_url)])
end
it 'reports healthy when the messaging webhook points at chatwoot' do
result = described_class.new(channel: channel).perform
expect(result[:status]).to eq('healthy')
expect(result[:webhooks]).to contain_exactly(hash_including(name: 'messaging', configured: true))
end
context 'when the messaging webhook points elsewhere' do
let(:sms_url) { 'https://demo.twilio.com/welcome/sms/reply' }
it 'reports misconfigured with the current url' do
result = described_class.new(channel: channel).perform
expect(result[:status]).to eq('misconfigured')
expect(result[:webhooks].first).to include(configured: false, expected: twilio_callback_index_url, actual: sms_url)
end
end
context 'when the number is missing from the twilio account' do
before { allow(numbers_list).to receive(:list).and_return([]) }
it 'raises' do
expect { described_class.new(channel: channel).perform }.to raise_error(/was not found/)
end
end
end
context 'with voice enabled' do
let(:channel) { create(:channel_twilio_sms, :with_voice) }
let(:sms_url) { nil }
let(:number) do
instance_double(NUMBER_INSTANCE, sms_url: sms_url, voice_url: channel.voice_call_webhook_url,
status_callback: channel.voice_status_webhook_url)
end
let(:twiml_app) { instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationContext) }
before do
allow(Twilio::VoiceWebhookSetupService).to receive(:new)
.and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}"))
allow(numbers_list).to receive(:list).and_return([number])
allow(twilio_client).to receive(:applications).and_return(twiml_app)
allow(twiml_app).to receive(:fetch).and_return(
instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationInstance, voice_url: twiml_app_voice_url)
)
end
context 'when everything is registered' do
let(:sms_url) { twilio_callback_index_url }
let(:twiml_app_voice_url) { channel.voice_call_webhook_url }
it 'reports healthy' do
result = described_class.new(channel: channel).perform
expect(result[:status]).to eq('healthy')
expect(result[:webhooks].map { |webhook| webhook[:name] }).to eq(%w[messaging voice voice_status voice_app])
end
end
context 'when the twiml app points at a stale host' do
let(:sms_url) { twilio_callback_index_url }
let(:twiml_app_voice_url) { 'https://old-host.example.com/twilio/voice/call/15551234567' }
it 'flags outbound calling as misconfigured even though the number is fine' do
result = described_class.new(channel: channel).perform
expect(result[:status]).to eq('misconfigured')
expect(result[:webhooks]).to include(hash_including(name: 'voice', configured: true),
hash_including(name: 'voice_app', configured: false))
end
end
context 'when the messaging webhook is missing' do
let(:twiml_app_voice_url) { channel.voice_call_webhook_url }
it 'flags messaging while voice stays configured' do
result = described_class.new(channel: channel).perform
expect(result[:status]).to eq('misconfigured')
expect(result[:webhooks]).to include(hash_including(name: 'messaging', configured: false),
hash_including(name: 'voice', configured: true))
end
end
end
end
end
@@ -23,7 +23,7 @@ describe Twilio::WebhookSetupService do
end
it 'updates the messaging service' do
described_class.new(inbox: channel_twilio_sms.inbox).perform
described_class.new(channel: channel_twilio_sms).perform
expect(services).to have_received(:update)
end
@@ -44,7 +44,7 @@ describe Twilio::WebhookSetupService do
allow(twilio_client).to receive(:incoming_phone_numbers).and_return(phone_double)
allow(phone_double).to receive(:list).and_return([])
described_class.new(inbox: channel_twilio_sms.inbox).perform
described_class.new(channel: channel_twilio_sms).perform
expect(phone_double).not_to have_received(:update)
end
@@ -53,7 +53,7 @@ describe Twilio::WebhookSetupService do
allow(twilio_client).to receive(:incoming_phone_numbers).and_return(phone_double)
allow(phone_double).to receive(:list).and_return([phone_record_double])
described_class.new(inbox: channel_twilio_sms.inbox).perform
described_class.new(channel: channel_twilio_sms).perform
expect(phone_double).to have_received(:update).with(
sms_method: 'POST',