Compare commits

...
Author SHA1 Message Date
Sivin VargheseandGitHub 725553d300 Merge branch 'develop' into fix/whatsapp-audio-mime-type 2025-11-14 14:47:14 +05:30
Vinay Keerthi 9d858a6f9b fix: Correct MIME type for WhatsApp audio files to prevent upload errors
WhatsApp Cloud API was rejecting audio forwards with Error 131053 because
audio/opus MIME type is unsupported. WhatsApp expects audio/ogg; codecs=opus
for .ogg files.

When receiving audio from WhatsApp, the file may be served with audio/opus
content-type header. This fix detects audio/opus + .ogg extension and corrects
it to audio/ogg; codecs=opus before storing in ActiveStorage.

Note: ActiveStorage's Marcel gem may still override this during analysis.
This is a known limitation that needs architectural discussion.

Fixes #12713
2025-11-12 19:29:30 +05:30
3 changed files with 65 additions and 1 deletions
@@ -127,11 +127,22 @@ class Whatsapp::IncomingMessageBaseService
file: {
io: attachment_file,
filename: attachment_file.original_filename,
content_type: attachment_file.content_type
content_type: corrected_content_type(attachment_file)
}
)
end
def corrected_content_type(attachment_file)
content_type = attachment_file.content_type
filename = attachment_file.original_filename
if content_type == 'audio/opus' && filename&.end_with?('.ogg')
'audio/ogg; codecs=opus'
else
content_type
end
end
def attach_location
location = @processed_params[:messages].first['location']
location_name = location['name'] ? "#{location['name']}, #{location['address']}" : ''
Binary file not shown.
@@ -38,6 +38,59 @@ describe Whatsapp::IncomingMessageWhatsappCloudService do
expect_message_has_attachment
end
it 'corrects audio/opus MIME type to audio/ogg; codecs=opus for .ogg files' do
audio_params = {
phone_number: whatsapp_channel.phone_number,
object: 'whatsapp_business_account',
entry: [{
changes: [{
value: {
contacts: [{ profile: { name: 'Sojan Jose' }, wa_id: '2423423243' }],
messages: [{
from: '2423423243',
id: 'audio-msg-123',
audio: {
id: 'audio-id-123',
mime_type: 'audio/ogg',
sha256: 'audio-sha256'
},
timestamp: '1664799904',
type: 'audio'
}]
}
}]
}]
}.with_indifferent_access
stub_request(:get, whatsapp_channel.media_url('audio-id-123', whatsapp_channel.provider_config['phone_number_id']))
.to_return(
status: 200,
body: { url: 'https://chatwoot-assets.local/sample.ogg' }.to_json,
headers: { 'content-type' => 'application/json' }
)
audio_file = Tempfile.new(['audio', '.ogg'])
audio_file.write(File.read('spec/assets/sample_opus.ogg'))
audio_file.rewind
audio_file.define_singleton_method(:content_type) { 'audio/opus' }
audio_file.define_singleton_method(:original_filename) { 'audio.ogg' }
allow(Down).to receive(:download)
.with('https://chatwoot-assets.local/sample.ogg', anything)
.and_return(audio_file)
service = described_class.new(inbox: whatsapp_channel.inbox, params: audio_params)
expect(service.send(:corrected_content_type, audio_file)).to eq('audio/ogg; codecs=opus')
service.perform
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
expect(whatsapp_channel.inbox.messages.count).to eq(1)
message = whatsapp_channel.inbox.messages.last
expect(message.attachments.first.file.content_type).to eq('audio/ogg; codecs=opus')
end
it 'increments reauthorization count if fetching attachment fails' do
stub_request(
:get,