From cbc28072966d57a78ba846fcb911e516dfb7846d Mon Sep 17 00:00:00 2001 From: Victor Eduardo Date: Mon, 28 Jul 2025 05:49:13 -0400 Subject: [PATCH] fix: Creates contact when Instagram returns `No matching Instagram user` (#11496) # Creates contact when Instagram returns `No matching Instagram user` ## Description The error occurs when Facebook tries to validate the Facebook App created to authorize Instagram integration. The Facebook's agent uses a Bot to make tests on the App where is not a valid user via API, returning `{"error"=>{"message"=>"No matching Instagram user", "type"=>"IGApiException", "code"=>9010}}`. Then Facebook rejects the request saying this app is still not ready once the integration with Instagram didn't work. We can safely create an unknown contact, making this integration work. ## Type of change Please delete options that are not relevant. - [X] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? There's automated test to cover. ## Checklist: - [X] My code follows the style guidelines of this project - [X] I have performed a self-review of my code - [X] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [X] I have added tests that prove my fix is effective or that my feature works - [X] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth --- app/services/instagram/message_text.rb | 26 +++++++++++++++---- .../instagram/messenger/message_text.rb | 10 +++++++ .../webhooks/instagram_events_job_spec.rb | 20 +++++++++++++- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/app/services/instagram/message_text.rb b/app/services/instagram/message_text.rb index e4b88c64d..3b371a2f4 100644 --- a/app/services/instagram/message_text.rb +++ b/app/services/instagram/message_text.rb @@ -14,10 +14,11 @@ class Instagram::MessageText < Instagram::BaseMessageText return process_successful_response(response) if response.success? - handle_error_response(response) - {} + handle_error_response(response, ig_scope_id) || {} end + private + def process_successful_response(response) result = JSON.parse(response.body).with_indifferent_access { @@ -32,8 +33,9 @@ class Instagram::MessageText < Instagram::BaseMessageText }.with_indifferent_access end - def handle_error_response(response) + def handle_error_response(response, ig_scope_id) parsed_response = response.parsed_response + parsed_response = JSON.parse(parsed_response) if parsed_response.is_a?(String) error_message = parsed_response.dig('error', 'message') error_code = parsed_response.dig('error', 'code') @@ -50,10 +52,17 @@ class Instagram::MessageText < Instagram::BaseMessageText # We can safely ignore this error. return if error_code == 230 - Rails.logger.warn("[InstagramUserFetchError]: account_id #{@inbox.account_id} inbox_id #{@inbox.id}") + # The error occurs when Facebook tries to validate the Facebook App created to authorize Instagram integration. + # The Facebook's agent uses a Bot to make tests on the App where is not a valid user via API, + # returning `{"error"=>{"message"=>"No matching Instagram user", "type"=>"IGApiException", "code"=>9010}}`. + # Then Facebook rejects the request saying this app is still not ready once the integration with Instagram didn't work. + # We can safely create an unknown contact, making this integration work. + return unknown_user(ig_scope_id) if error_code == 9010 + + Rails.logger.warn("[InstagramUserFetchError]: account_id #{@inbox.account_id} inbox_id #{@inbox.id} ig_scope_id #{ig_scope_id}") Rails.logger.warn("[InstagramUserFetchError]: #{error_message} #{error_code}") - exception = StandardError.new("#{error_message} (Code: #{error_code})") + exception = StandardError.new("#{error_message} (Code: #{error_code}, IG Scope ID: #{ig_scope_id})") ChatwootExceptionTracker.new(exception, account: @inbox.account).capture_exception end @@ -66,4 +75,11 @@ class Instagram::MessageText < Instagram::BaseMessageText Messages::Instagram::MessageBuilder.new(@messaging, @inbox, outgoing_echo: agent_message_via_echo?).perform end + + def unknown_user(ig_scope_id) + { + 'name' => "Unknown (IG: #{ig_scope_id})", + 'id' => ig_scope_id + }.with_indifferent_access + end end diff --git a/app/services/instagram/messenger/message_text.rb b/app/services/instagram/messenger/message_text.rb index ed544263e..f383bcdca 100644 --- a/app/services/instagram/messenger/message_text.rb +++ b/app/services/instagram/messenger/message_text.rb @@ -33,6 +33,16 @@ class Instagram::Messenger::MessageText < Instagram::BaseMessageText return end + # Handle error code 9010: No matching Instagram user + # This occurs when trying to fetch an Instagram user that doesn't exist or is not accessible + # We can safely ignore this error and return empty result + if error.message.include?('9010') + Rails.logger.warn("[Instagram User Not Found]: account_id #{@inbox.account_id} inbox_id #{@inbox.id}") + Rails.logger.warn("[Instagram User Not Found]: #{error.message}") + Rails.logger.warn("[Instagram User Not Found]: #{error}") + return + end + Rails.logger.warn("[FacebookUserFetchClientError]: account_id #{@inbox.account_id} inbox_id #{@inbox.id}") Rails.logger.warn("[FacebookUserFetchClientError]: #{error.message}") ChatwootExceptionTracker.new(error, account: @inbox.account).capture_exception diff --git a/spec/jobs/webhooks/instagram_events_job_spec.rb b/spec/jobs/webhooks/instagram_events_job_spec.rb index dfd9f1aed..1962c78a5 100644 --- a/spec/jobs/webhooks/instagram_events_job_spec.rb +++ b/spec/jobs/webhooks/instagram_events_job_spec.rb @@ -279,11 +279,29 @@ describe Webhooks::InstagramEventsJob do expect(instagram_inbox.messages.count).to be 0 end - it 'handle messaging_seen callback' do + it 'handles messaging_seen callback' do expect(Instagram::ReadStatusService).to receive(:new).with(params: message_events[:messaging_seen][:entry][0][:messaging][0], channel: instagram_inbox.channel).and_call_original instagram_webhook.perform_now(message_events[:messaging_seen][:entry]) end + + it 'creates contact when Instagram API call returns `No matching Instagram user` (9010 error code)' do + stub_request(:get, %r{https://graph\.instagram\.com/v22\.0/.*\?.*}) + .to_return(status: 401, body: { error: { message: 'No matching Instagram user', code: 9010 } }.to_json) + + instagram_webhook.perform_now(message_events[:dm][:entry]) + + instagram_inbox.reload + + expect(instagram_inbox.contacts.count).to be 1 + expect(instagram_inbox.contacts.last.name).to eq 'Unknown (IG: Sender-id-1)' + expect(instagram_inbox.contacts.last.contact_inboxes.count).to be 1 + expect(instagram_inbox.contacts.last.contact_inboxes.first.source_id).to eq 'Sender-id-1' + + expect(instagram_inbox.conversations.count).to eq 1 + expect(instagram_inbox.messages.count).to eq 1 + expect(instagram_inbox.messages.last.content_attributes['is_unsupported']).to be_nil + end end end end