Class: Facebook::ApiClient
- Inherits:
-
BaseService
- Object
- BaseService
- Facebook::ApiClient
- Defined in:
- app/services/facebook/api_client.rb
Overview
Faraday-backed HTTP client for the Facebook/Meta Conversions API (CAPI).
Mirrors Pinterest::ApiClient and OpenaiAds::ApiClient — keeps
ConversionReporter free of HTTP concerns so we can stub it
cleanly in tests.
CAPI events POST to a Pixel (not an ad account): the URL takes the
pixel id as a path segment (/{pixel_id}/events). Auth is a System
User access token; Meta's docs show it as ?access_token= query param
but Bearer header also works and avoids leaking the token into request
logs — we follow the Bearer pattern used by Pinterest and OpenAI.
Constant Summary collapse
- API_VERSION =
Marketing Graph API version. Bump in lockstep with
Facebook::AdvertiserApiClient::API_VERSION. 'v25.0'- BASE_URL =
URL for base.
"https://graph.facebook.com/#{API_VERSION}".freeze
- TRANSIENT_META_ERROR_CODES =
Meta Graph API error codes that mean "retry later", not "your request
is wrong" — so they get retried (via ConversionRetrySweepWorker)
rather than paged as a hard error. Two families:- Temporary Meta-side faults: 1 ("An unknown error has occurred.")
and 2 ("Service temporarily unavailable") — these dominated
AppSignal #5248 once PR #1172's diagnostics made the codes visible. - Throttling / rate limits: 4 (app-level), 17 (user-level),
32 (page-level), 613 (custom-level) — all transient back-pressure.
Any HTTP 5xx is likewise transient.
- Temporary Meta-side faults: 1 ("An unknown error has occurred.")
[1, 2, 4, 17, 32, 613].freeze
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
-
#send_events(pixel_id:, token:, events:, test_event_code: nil) ⇒ Hash
Send one or more conversion events to a Facebook Pixel.
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #process, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Instance Method Details
#send_events(pixel_id:, token:, events:, test_event_code: nil) ⇒ Hash
Send one or more conversion events to a Facebook Pixel.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'app/services/facebook/api_client.rb', line 53 def send_events(pixel_id:, token:, events:, test_event_code: nil) body = { data: events } body[:test_event_code] = test_event_code if test_event_code.present? response = connection(token).post("#{pixel_id}/events") do |req| req.body = body.to_json end case response.status when 200 if test_event_code.present? Rails.logger.info "Facebook::ApiClient: test_event_code=#{test_event_code} payload accepted (HTTP 200)" { status: :test_event_ok, http_status: 200 } else Rails.logger.info 'Facebook::ApiClient: events accepted (HTTP 200)' { status: :reported, http_status: 200 } end when 429 error_msg = 'Rate limited (HTTP 429)' Rails.logger.warn "Facebook::ApiClient: #{error_msg}" { status: :rate_limited, http_status: 429, error: error_msg } else body = safe_parse(response.body) = body['error'].is_a?(Hash) ? body['error'] : {} error_msg = ['message'] || body['message'] || "HTTP #{response.status}" transient = (response.status, ['code']) # Surface Meta's diagnostic fields. "An unknown error has occurred." is # Meta's OWN generic message (Graph error code 1/2 = transient, "retry # later"); without the code/subcode/fbtrace_id the report is undiagnosable # (AppSignal #5248). Pass them through so transient-vs-real is visible. # `transient` keeps status :failed (so ConversionRetrySweepWorker still # re-enqueues it) while letting the reporter skip AppSignal noise for # these Meta-side blips — see {Facebook::ConversionReporter#send_events}. Rails.logger.public_send(transient ? :warn : :error, "Facebook::ApiClient: #{transient ? 'Transient' : 'Failed'} -- #{error_msg} " \ "(code=#{['code']} subcode=#{['error_subcode']} " \ "fbtrace_id=#{['fbtrace_id']} http=#{response.status})") { status: :failed, transient: transient, http_status: response.status, error: error_msg, error_code: ['code'], error_subcode: ['error_subcode'], fbtrace_id: ['fbtrace_id'] } end rescue Faraday::TimeoutError => e # Network blip to Meta — keep status :failed so the sweep re-enqueues it, # flag transient so the reporter doesn't page AppSignal per attempt (#5248). { status: :failed, transient: true, http_status: nil, error: "Timeout: #{e.}" } rescue Faraday::ConnectionFailed => e { status: :failed, transient: true, http_status: nil, error: "Connection failed: #{e.}" } rescue Faraday::Error => e # Catch-all for the rest of the Faraday hierarchy — Faraday::SSLError # (TLS/cert misconfig) and friends are client-side defects, NOT transient # blips. Leave `transient` unset so the reporter pages AppSignal instead # of silently retrying a broken config forever. { status: :failed, http_status: nil, error: e. } end |