we-promise/sure · error · Provider::Sophtron::Error
invalid_access_key
invalid_access_key
Error message
Invalid Sophtron Access Key: #{e.message} What it means
Raised by Provider::Sophtron#auth_header_for when building the FIApiAUTH HMAC-SHA256 signature fails with an ArgumentError - most commonly because Base64-decoding the stored access_key produced empty bytes ('decoded key is empty'). The Sophtron access key must be a Base64-encoded HMAC key; a blank, nil, or non-key string fails here before any HTTP request is made.
Source
Thrown at app/models/provider/sophtron.rb:42
attr_reader :user_id, :access_key, :base_url
def initialize(user_id, access_key, base_url: DEFAULT_BASE_URL)
@user_id = user_id
@access_key = access_key
@base_url = normalize_base_url(base_url)
super()
end
def auth_header_for(method, api_path)
auth_path = self.class.auth_path(api_path)
plain_key = "#{method.to_s.upcase}\n#{auth_path}"
key_bytes = Base64.decode64(access_key.to_s)
raise ArgumentError, "decoded key is empty" if key_bytes.blank?
signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), key_bytes, plain_key)
"FIApiAUTH:#{user_id}:#{Base64.strict_encode64(signature)}:#{auth_path}"
rescue ArgumentError => e
raise Error.new("Invalid Sophtron Access Key: #{e.message}", :invalid_access_key)
end
def self.auth_path(api_path)
path = URI.parse(api_path.to_s).path
last_segment = path.to_s.split("/").last.to_s
"/#{last_segment}".downcase
rescue URI::InvalidURIError
last_segment = api_path.to_s.split("?").first.to_s.split("/").last.to_s
"/#{last_segment}".downcase
end
def self.job_success?(job)
job = job.with_indifferent_access
job[:SuccessFlag] == true || job[:success_flag] == true || job[:LastStatus].to_s == "AccountsReady" || job[:last_status].to_s == "AccountsReady"
end
def self.job_failed?(job)
job = job.with_indifferent_accessView on GitHub (pinned to e69894adb9)
Solutions
- Check that the access_key passed to Provider::Sophtron.new is present and non-blank in the failing environment
- Re-copy the key from the Sophtron dashboard, ensuring it is the Base64 access key (not the user ID), with no trailing newline
- Verify it decodes to non-empty bytes: Base64.decode64(key).length must be > 0 before constructing the client
- After fixing the key, confirm end-to-end with a lightweight call - if a 401 follows, the user_id is the wrong half of the pair
Example fix
# before - constructing the client with possibly-missing config client = Provider::Sophtron.new(user_id, ENV["SOPHTRON_ACCESS_KEY"]) # after - fail fast on an unusable key key = ENV["SOPHTRON_ACCESS_KEY"].to_s raise ArgumentError, "SOPHTRON_ACCESS_KEY missing or empty" if Base64.decode64(key).bytesize.zero? client = Provider::Sophtron.new(user_id, key)
Defensive patterns
Strategy: validation
Validate before calling
key = credentials.access_key.to_s if key.blank? || Base64.decode64(key).bytesize.zero? raise ArgumentError, "Sophtron access key is missing or decodes empty - check SOPHTRON_ACCESS_KEY" end client = Provider::Sophtron.new(credentials.user_id, key)
Type guard
def usable_sophtron_key?(key) decoded = Base64.decode64(key.to_s) !key.to_s.blank? && decoded.bytesize.positive? end
Try / catch
begin client.get_accounts rescue Provider::Sophtron::Error => e raise ConfigError, "Re-link Sophtron credentials" if e.error_type == :invalid_access_key raise end
Prevention
- Assert at boot that SOPHTRON_ACCESS_KEY decodes to non-empty bytes
- Strip whitespace/newlines when storing pasted keys
- Fail configuration checks during deploy, not at the first user-triggered sync
When it happens
Trigger: Instantiating Provider::Sophtron with an empty/nil access_key (missing SOPHTRON_ACCESS_KEY env var), a placeholder value, or a string whose Base64 decode yields zero bytes; every request method calls auth_headers -> auth_header_for, so the first API call after construction raises.
Common situations: SOPHTRON_ACCESS_KEY never set in a new environment (staging/CI); key cleared during a credentials rotation but not replaced; whitespace/newline pasted with the key making decode succeed-but-wrong (then auth fails as 401 instead); key set on a different Rails env than the one running.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/df35f4107d29f19a.
Report an issue: GitHub.