From 4c4e5a903897375a1718190b466aecd77dc4e974 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 16:12:28 -0400 Subject: [PATCH 1/8] DX-3359 Update Sample App --- Gemfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 9147bd1..d4f81ee 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,6 @@ source 'https://rubygems.org' -gem 'bandwidth-sdk', '11.0.0' -gem 'sinatra', '3.1.0' +gem 'bandwidth-sdk', '18.3.1' +gem 'puma', '8.0.2' +gem 'rackup', '2.3.1' +gem 'sinatra', '4.2.1' From abf0124e586311a0497420d9335c65c9dd1d41c0 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 16:12:35 -0400 Subject: [PATCH 2/8] add test workflow --- .github/workflows/test.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5c1f34d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: Test + +on: + pull_request: + branches: + - main + schedule: + - cron: "0 9 * * 1" + +jobs: + contract-test: + name: Contract Test + uses: Bandwidth-Samples/sample-app-tooling/.github/workflows/ruby.yml@v1 + with: + sample: voice-record From f70d28074b5749570e78b5edaa19068d856f87c7 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 16:20:52 -0400 Subject: [PATCH 3/8] update app --- app.rb | 64 +++++++++++++++++++++++-------------------- Tone.mp3 => tone.mp3 | Bin 2 files changed, 35 insertions(+), 29 deletions(-) rename Tone.mp3 => tone.mp3 (100%) diff --git a/app.rb b/app.rb index 898c4e6..c27febe 100644 --- a/app.rb +++ b/app.rb @@ -2,56 +2,62 @@ require 'bandwidth-sdk' begin - BW_USERNAME = ENV.fetch('BW_USERNAME') - BW_PASSWORD = ENV.fetch('BW_PASSWORD') BW_ACCOUNT_ID = ENV.fetch('BW_ACCOUNT_ID') - LOCAL_PORT = ENV.fetch('LOCAL_PORT') + BW_CLIENT_ID = ENV.fetch('BW_CLIENT_ID') + BW_CLIENT_SECRET = ENV.fetch('BW_CLIENT_SECRET') + LOCAL_PORT = Integer(ENV.fetch('LOCAL_PORT')) rescue StandardError puts 'Please set the environmental variables defined in the README' exit(-1) end set :port, LOCAL_PORT +set :host_authorization, permitted_hosts: [] -Bandwidth.configure do |config| # Configure Basic Auth - config.username = BW_USERNAME - config.password = BW_PASSWORD +Bandwidth.configure do |config| + config.client_id = BW_CLIENT_ID + config.client_secret = BW_CLIENT_SECRET config.return_binary_data = true end -post '/callbacks/callInitiatedCallback' do +def parse_callback(expected_event_type) + data = JSON.parse(request.body.read) + halt 400 unless data.is_a?(Hash) && data['eventType'] == expected_event_type + data +rescue JSON::ParserError + halt 400 +end + +post '/callbacks/inbound/voice' do + parse_callback('initiate') + unavailable_speak_sentence = Bandwidth::Bxml::SpeakSentence.new('You have reached Vandelay Industries, Kal Varnsen is unavailable at this time.') message_speak_sentence = Bandwidth::Bxml::SpeakSentence.new('At the tone, please record your message, when you have finished recording, you may hang up.') - play_audio = Bandwidth::Bxml::PlayAudio.new('Tone.mp3') - record = Bandwidth::Bxml::Record.new({ recording_available_url: '/callbacks/recordingAvailableCallback' }) - response = Bandwidth::Bxml::Response.new([unavailable_speak_sentence, message_speak_sentence, play_audio, record]) + play_audio = Bandwidth::Bxml::PlayAudio.new('/tone.mp3') + record = Bandwidth::Bxml::Record.new({ recording_available_url: '/callbacks/inbound/voice/recordingAvailable', max_duration: 30 }) + bxml = Bandwidth::Bxml::Response.new([unavailable_speak_sentence, message_speak_sentence, play_audio, record]) - return response.to_bxml + content_type 'application/xml' + bxml.to_bxml end -get '/callbacks/Tone.mp3' do - response.headers['Content-Type'] = 'audio/mp3' - - tone_file = File.open('Tone.mp3') - file_data = tone_file.read - tone_file.close - file_data +get '/tone.mp3' do + send_file 'tone.mp3', type: 'audio/mpeg' end -post '/callbacks/recordingAvailableCallback' do - data = JSON.parse(request.body.read) - - if data['eventType'] == 'recordingAvailable' - recordings_api = Bandwidth::RecordingsApi.new +post '/callbacks/inbound/voice/recordingAvailable' do + data = parse_callback('recordingAvailable') - file_format = data['fileFormat'] - call_id = data['callId'] - recording_id = data['recordingId'] + begin + recording = Bandwidth::RecordingsApi.new.download_call_recording(BW_ACCOUNT_ID, data['callId'], data['recordingId']) - recording = recordings_api.download_call_recording(BW_ACCOUNT_ID, call_id, recording_id) - - File.open("#{recording_id}.#{file_format}", 'wb') do |file| + File.open("#{data['recordingId']}.#{data['fileFormat']}", 'wb') do |file| file.write(recording) end + rescue StandardError => e + warn "Failed to download recording: #{e.message}" + halt 500 end + + status 204 end diff --git a/Tone.mp3 b/tone.mp3 similarity index 100% rename from Tone.mp3 rename to tone.mp3 From 69f7b6d5ce46a6bfccb014a233aca1d0b182c45b Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 16:22:18 -0400 Subject: [PATCH 4/8] update readme --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5e53219..c53d144 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ This sample app demonstrates how to record a message from an inbound call, similar to how a voicemail machine would work. -When the user calls the Bandwidth phone number associated with this application, the user will hear an audio prompt to leave a message, then a beep. Once this happens, the call will begin recording. +When the user calls the Bandwidth phone number associated with this application, the user will hear an audio prompt to leave a message, then a beep. Once this happens, the call will begin recording, for up to 30 seconds. -When the call ends, Bandwidth will start to process the recording. Once this processing is complete, Bandwidth will send a webhook to the /recordingAvailableCallback endpoint on this application. Once received, the application will download the recording via Bandwidth's voice recording API. +When the call ends, Bandwidth will start to process the recording. Once this processing is complete, Bandwidth will send a webhook to the `/callbacks/inbound/voice/recordingAvailable` endpoint on this application. Once received, the application will download the recording via Bandwidth's voice recording API. # Pre-Requisites @@ -48,8 +48,8 @@ The sample app uses the below environmental variables. ```sh BW_ACCOUNT_ID # Your Bandwidth Account Id -BW_USERNAME # Your Bandwidth API Username -BW_PASSWORD # Your Bandwidth API Password +BW_CLIENT_ID # Your Bandwidth API OAuth Client Id +BW_CLIENT_SECRET # Your Bandwidth API OAuth Client Secret LOCAL_PORT # The port number you wish to run the sample on ``` @@ -58,8 +58,10 @@ LOCAL_PORT # The port number you wish to run the sampl For a detailed introduction, check out our [Bandwidth Voice Callbacks](https://dev.bandwidth.com/docs/voice/webhooks) page. Below are the callback paths: -* `/callbacks/callInitiatedCallback` -* `/callbacks/recordingAvailableCallback` +* `/callbacks/inbound/voice` +* `/callbacks/inbound/voice/recordingAvailable` + +The app also serves the prompt tone at `/tone.mp3`, which is not a callback. ## Ngrok @@ -70,4 +72,4 @@ After you have downloaded and installed `ngrok` run the following command to ope ngrok http $LOCAL_PORT ``` -You can view your public URL at `http://127.0.0.1:4040` after ngrok is running. You can also view the status of the tunnel and requests/responses here. Once your public ngrok url has been created, you can use it as the `BASE_CALLBACK_URL` environmental variable and set it in the voice application created in the [Pre-Requisites](#pre-requisites) section. +You can view your public URL at `http://127.0.0.1:4040` after ngrok is running. You can also view the status of the tunnel and requests/responses here. Once your public ngrok url has been created, you can set it, followed by `/callbacks/inbound/voice`, as the callback URL of the voice application created in the [Pre-Requisites](#pre-requisites) section. From 4c2b7056e72de0979ca2415a9dd86417537752b8 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 16:35:23 -0400 Subject: [PATCH 5/8] hit main for now --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c1f34d..76198e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,6 @@ on: jobs: contract-test: name: Contract Test - uses: Bandwidth-Samples/sample-app-tooling/.github/workflows/ruby.yml@v1 + uses: Bandwidth-Samples/sample-app-tooling/.github/workflows/ruby.yml@main with: sample: voice-record From 13ce30339d1e39bd3f4d20024db19b85f36b3196 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 17:07:49 -0400 Subject: [PATCH 6/8] update codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index beb9074..d67a43f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Global rule: -* @Bandwidth-Samples/dx +* @Bandwidth-Samples/band-swi From 029f83924df97d0e18ab48048992b59146d38f0f Mon Sep 17 00:00:00 2001 From: ckoegel Date: Fri, 16 Oct 2026 17:29:57 -0400 Subject: [PATCH 7/8] dx --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d67a43f..12776e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Global rule: -* @Bandwidth-Samples/band-swi +* @Bandwidth-Samples/band-swi @Bandwidth-Samples/band-devx From 620cec37d5ea5952bbac9d0a1bd4529e2a20cc02 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Mon, 19 Oct 2026 13:48:37 -0400 Subject: [PATCH 8/8] add dependabot --- .github/dependabot.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8350171 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 + +updates: + - package-ecosystem: bundler + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 5 + ignore: + - dependency-name: bandwidth-sdk + update-types: + - version-update:semver-minor + - version-update:semver-patch + groups: + dependencies: + patterns: + - "*" + exclude-patterns: + - bandwidth-sdk