we-promise/sure · error · ActionController::BadRequest
invalid credential payload
Error message
invalid credential payload
What it means
This is the companion guard to "credential must be an object" in webauthn_credential_payload. When the credential param arrives as a String, the helper runs JSON.parse on it; if parsing blows up it is rescued (JSON::ParserError, TypeError, ArgumentError) and re-raised as ActionController::BadRequest (HTTP 400) with "invalid credential payload". It fires specifically when the string is present and shaped like a string but is not syntactically valid JSON — malformed structure, not wrong shape after parsing.
Source
Thrown at app/controllers/concerns/webauthn_relying_party.rb:29
name: "Sure",
id: webauthn_config.rp_id,
allowed_origins: webauthn_config.allowed_origins,
# Accept consumer passkeys/security keys without attesting device vendor
# identity; this keeps MFA registration broad for self-hosted users.
verify_attestation_statement: false
)
end
def webauthn_credential_payload
payload = params.require(:credential)
payload = JSON.parse(payload) if payload.is_a?(String)
payload = payload.to_unsafe_h if payload.respond_to?(:to_unsafe_h)
raise ActionController::BadRequest, "credential must be an object" unless payload.is_a?(Hash)
payload
rescue JSON::ParserError, TypeError, ArgumentError
raise ActionController::BadRequest, "invalid credential payload"
end
end
View on GitHub (pinned to e69894adb9)
Solutions
- Validate the string client-side before sending: JSON.parse(credentialStr) must succeed and yield an object
- Send via fetch with JSON.stringify so escaping is handled by the serializer, never by string concatenation
- Check for body size limits / WAF rules if payloads are large passkey responses getting truncated mid-flight
- In Ruby clients, generate the payload with JSON.generate rather than heredocs or interpolation
Example fix
# before (broken client)
body = "credential=" + json.to_s.tr("\"", "'") # single-quoted pseudo-JSON
# => JSON::ParserError rescued -> 400 "invalid credential payload"
# after
body = { credential: JSON.generate(json) }.to_json
# server: JSON.parse succeeds -> Hash -> proceeds to WebAuthn verification Defensive patterns
Strategy: validation
Validate before calling
# Ruby client, before sending require "json" def valid_credential_json?(str) JSON.parse(str).is_a?(Hash) rescue JSON::ParserError, TypeError false end payload = JSON.generate(credential) # serialize, never concatenate strings
Type guard
def valid_credential_json?(str) JSON.parse(str).is_a?(Hash) rescue JSON::ParserError, TypeError false end
Try / catch
rescue ActionController::BadRequest => e # 400 with "invalid credential payload": the string was syntactically broken. # Re-serialize client-side with JSON.generate and retry once. head :bad_request end
Prevention
- Generate JSON with a serializer (JSON.generate / JSON.stringify), never string interpolation
- Avoid shell heredocs for payloads; use files written by the serializer
- Watch for proxies/WAFs truncating large passkey payloads
- Retry once only after re-serializing; identical retries of broken JSON always fail
When it happens
Trigger: credential param sent as a truncated JSON string (missing closing brace, e.g. from a URL-truncated form value); single quotes instead of double quotes (credential="{'id': 1}"); unescaped newlines/control characters inside the JSON; a client sending url-encoded JSON that got double-escaped (%7B%22id%22…) so JSON.parse sees percent-encoded junk; nil propagated into JSON.parse raises TypeError and lands here too.
Common situations: Hand-rolled mobile clients or curl scripts with shell quoting bugs; proxies/WAFs that rewrite or truncate long request bodies (passkey payloads are large); frontend code that concatenates the credential JSON into a FormData value without encoding; a test that builds the string with string interpolation producing invalid JSON.
Related errors
- credential must be an object
- Could not save that passkey or security key. Please try agai
- parse_error
- bad_response
- bad_request
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/5dd3a67cb29877a6.
Report an issue: GitHub.