fix(whatsapp): retry webhook setup on transient failures before prompting reauthorization

This commit is contained in:
Tanmay Deep Sharma
2026-07-22 10:44:10 +05:30
parent 7b732e6a16
commit b1acd38c10
4 changed files with 67 additions and 21 deletions
@@ -1,10 +1,21 @@
class Channels::Whatsapp::WebhookSetupJob < ApplicationJob
queue_as :low
# Runs Meta's phone-registration and webhook-subscription calls off the request
# thread. Inline, these Graph API calls can exceed the 15s Rack::Timeout and, since
# RequestTimeoutException bypasses setup_webhooks' rescue, abort inbox creation and
# roll it back — leaving the number connected on Meta but no inbox in Chatwoot.
# Meta's Graph API calls (phone registration + webhook subscription) are slow and fail
# transiently. Retry a few times, and once retries are exhausted mark the channel for
# reauthorization so the inbox has a visible recovery path instead of silently missing its
# webhook subscription. Running these off the request thread also keeps them clear of the
# 15s Rack::Timeout, which previously aborted inbox creation and rolled it back.
retry_on StandardError, wait: :polynomially_longer, attempts: 3 do |job, error|
channel = job.arguments.first
Rails.logger.error("[WHATSAPP] Webhook setup failed after retries: #{error.message}")
channel.prompt_reauthorization! if channel.is_a?(Channel::Whatsapp)
end
# A deleted channel can't be set up; discard instead of retrying (takes precedence over
# the StandardError retry above, which would otherwise catch DeserializationError too).
discard_on ActiveJob::DeserializationError
def perform(whatsapp_channel, run_health_check: false)
whatsapp_channel.setup_webhooks
# Health check runs only after registration so a freshly provisioned number
+3 -5
View File
@@ -120,13 +120,11 @@ class Channel::Whatsapp < ApplicationRecord
delegate :media_url, to: :provider_service
delegate :api_headers, to: :provider_service
# Runs synchronously inside Channels::Whatsapp::WebhookSetupJob; the Meta Graph
# calls are kept off the request thread so a slow response can't trip Rack::Timeout.
# Runs inside Channels::Whatsapp::WebhookSetupJob, off the request thread so the slow Meta
# Graph calls can't trip Rack::Timeout. Raises on failure so the job can retry the flaky
# calls and, once retries are exhausted, mark the channel for reauthorization.
def setup_webhooks
perform_webhook_setup
rescue StandardError => e
Rails.logger.error "[WHATSAPP] Webhook setup failed: #{e.message}"
prompt_reauthorization!
end
# Enqueue on the same channel record so GlobalID resolves it in the job. If the queue
@@ -0,0 +1,49 @@
require 'rails_helper'
RSpec.describe Channels::Whatsapp::WebhookSetupJob do
let(:channel) do
create(:channel_whatsapp, validate_provider_config: false, sync_templates: false)
end
it 'runs webhook setup' do
allow(channel).to receive(:setup_webhooks)
described_class.perform_now(channel)
expect(channel).to have_received(:setup_webhooks)
end
it 'runs the provisioning health check when requested' do
allow(channel).to receive(:setup_webhooks)
allow(channel).to receive(:check_provisioning_health)
described_class.perform_now(channel, run_health_check: true)
expect(channel).to have_received(:check_provisioning_health)
end
it 'skips the provisioning health check by default' do
allow(channel).to receive(:setup_webhooks)
allow(channel).to receive(:check_provisioning_health)
described_class.perform_now(channel)
expect(channel).not_to have_received(:check_provisioning_health)
end
context 'when webhook setup keeps failing' do
before do
setup_service = instance_double(Whatsapp::WebhookSetupService)
allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(setup_service)
allow(setup_service).to receive(:perform).and_raise(StandardError, 'meta down')
end
it 'retries and marks the channel for reauthorization once retries are exhausted' do
expect(channel.reauthorization_required?).to be false
perform_enqueued_jobs { described_class.perform_later(channel) }
expect(channel.reauthorization_required?).to be true
end
end
end
@@ -67,18 +67,6 @@ describe Whatsapp::EmbeddedSignupService do
expect(Rails.logger).to receive(:error).with('[WHATSAPP] Embedded signup failed: Token error')
expect { service.perform }.to raise_error('Token error')
end
# Webhook setup now runs in Channels::Whatsapp::WebhookSetupJob; the channel's own
# setup_webhooks rescue marks it for reauthorization when the Meta calls fail.
it 'marks the channel for reauthorization when webhook setup fails' do
real_channel = create(:channel_whatsapp, account: account, phone_number: '+1234567890',
validate_provider_config: false, sync_templates: false)
allow(real_channel).to receive(:perform_webhook_setup).and_raise('Webhook setup error')
expect(real_channel.reauthorization_required?).to be false
real_channel.setup_webhooks
expect(real_channel.reauthorization_required?).to be true
end
end
context 'with reauthorization flow' do