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_sView on GitHub (pinned to e69894adb9)
Solutions
- Generate a fresh setup token from SimpleFin and claim that one exactly once
- Make claim idempotent in your flow: persist the claimed access URL keyed by token so retries reuse it instead of re-claiming
- Never auto-retry a 403 claim — request a new token from the user instead
- 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
- Persist claimed access URLs keyed by token digest so job retries never re-claim
- Make the claim step exactly-once in the UI: disable the submit button after first click
- Never auto-retry 403 claims — always ask the user for a new setup token
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
- claim_failed
- access_forbidden
- Please provide a name and a complete US address (street, cit
- access_forbidden
- access_forbidden
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/23b339fef570092e.
Report an issue: GitHub.