we-promise/sure · critical · Provider::Simplefin::SimplefinError

token_compromised

token_compromised

Error message

Setup token may be compromised, expired, or already used

What it means

Raised by Provider::Simplefin#claim_access_url when POSTing the decoded claim URL returns 403, with error_type :token_compromised. SimpleFin setup tokens are one-time, short-lived claim tokens: a 403 means the token was already claimed, has expired, or was flagged compromised. Each setup token can produce exactly one access URL; claiming is not repeatable.

Source

Thrown at app/models/provider/simplefin.rb:48

  end

  def claim_access_url(setup_token)
    # Decode the base64 setup token to get the claim URL
    claim_url = Base64.decode64(setup_token)

    # Use retry logic for transient network failures during token claim
    # Claim should be fast; keep request-path latency bounded.
    # Use self.class.post to inherit class-level SSL and timeout defaults
    response = with_retries("POST /claim", max_retries: 1, backoff: false) do
      self.class.post(claim_url, timeout: 15)
    end

    case response.code
    when 200
      # The response body contains the access URL with embedded credentials
      response.body.strip
    when 403
      raise SimplefinError.new("Setup token may be compromised, expired, or already used", :token_compromised)
    else
      raise SimplefinError.new("Failed to claim access URL: #{response.code} #{response.message}", :claim_failed)
    end
  end

  def get_accounts(access_url, start_date: nil, end_date: nil, pending: nil)
    # Build query parameters
    query_params = {}

    # SimpleFin expects Unix timestamps for dates
    if start_date
      start_timestamp = start_date.to_time.to_i
      query_params["start-date"] = start_timestamp.to_s
    end

    if end_date
      end_timestamp = end_date.to_time.to_i
      query_params["end-date"] = end_timestamp.to_s

View on GitHub (pinned to e69894adb9)

Solutions

  1. Generate a fresh setup token from SimpleFin and claim that one exactly once
  2. Make claim idempotent in your flow: persist the claimed access URL keyed by token so retries reuse it instead of re-claiming
  3. Never auto-retry a 403 claim — request a new token from the user instead
  4. If the user is certain the token is fresh, have them re-generate; SimpleFin support can confirm compromise flags

Example fix

# before
access_url = client.claim_access_url(setup_token) # retried job claims twice -> 403

# after
access_url = ClaimedToken.where(setup_token_digest: Digest::SHA256.hexdigest(setup_token)).pick(:access_url)
access_url ||= ClaimedToken.create!(setup_token_digest: Digest::SHA256.hexdigest(setup_token), access_url: client.claim_access_url(setup_token)).access_url
Defensive patterns

Strategy: validation

Validate before calling

def plausible_setup_token?(setup_token)
  decoded = Base64.decode64(setup_token.to_s)
  decoded.start_with?("https://") && !decoded.include?(" ")
rescue ArgumentError
  false
end

raise ArgumentError, "setup token does not decode to a claim URL" unless plausible_setup_token?(setup_token)

Type guard

def simplefin_token_compromised?(error)
  error.is_a?(Provider::Simplefin::SimplefinError) && error.error_type == :token_compromised
end

Try / catch

begin
  access_url = client.claim_access_url(setup_token)
rescue Provider::Simplefin::SimplefinError => e
  raise unless e.error_type == :token_compromised
  clear_pending_claim!(user) # one-time token is burned; require a fresh one
end

Prevention

When it happens

Trigger: Calling claim_access_url twice with the same base64 setup token (double-clicked submit, retried job, duplicate webhook); a stale token past its validity window; the user re-issuing the token invalidating the older copy.

Common situations: Idempotency bug where a background job retries the claim after a partially failed first attempt; user pastes an old token from a previous attempt; clock gaps between token generation and claim.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/23b339fef570092e. Report an issue: GitHub.