instructure/canvas-lms · error · BasicLTI::Errors::InvalidSourceId
tool_invalid
tool_invalid
Error message
Tool is invalid
What it means
BasicLTI::BasicOutcomes.decode_source_id loads the signed sourcedid and compares its embedded tool with the tool the request was authenticated as. If they differ, the request is from a different tool than the one that issued the sourcedid, so InvalidSourceId with code tool_invalid is raised.
Solutions
- Ensure the tool authenticating the outcomes request is the same one that generated the sourcedid (same consumer key)
- Re-issue the LTI launch so the student gets a fresh sourcedid bound to the current tool
- Check for duplicated or re-keyed ContextExternalTool records and consolidate them
- Verify the TP is not replaying sourcedids captured from another tool's launches
Example fix
// before tool = ContextExternalTool.find_by(consumer_key: shared_key) // may differ from sourcedid tool // after tool = BasicLTI::Sourcedid.load!(sourceid).tool // use the sourcedid's own tool
Defensive patterns
Strategy: try-catch
Validate before calling
sourcedid = BasicLTI::Sourcedid.load!(sourceid) raise "tool mismatch" unless sourcedid.tool.id == tool.id
Try / catch
begin assignment, user = BasicLTI::BasicOutcomes.decode_source_id(tool, sourceid) rescue BasicLTI::Errors::InvalidSourceId => e return xml failure response for code e.code end
Prevention
- Authenticate the outcomes request with the same consumer key that issued the sourcedid
- Avoid recreating/re-keying tools mid-lifecycle
- Return a typed failure (tool_invalid) to the TP instead of a 500
When it happens
Trigger: A Basic Outcomes (grade replace) XML request authenticated with a different OAuth consumer key/tool than the one encoded in the lis_result_sourcedid; a tool re-keyed or recreated so its id no longer matches; sourcedid copied between tool contexts.
Common situations: Shared consumer keys across multiple tool deployments; a tool re-registered with a new key mid-flight so old sourcedids validate against the new tool object; TP forwarding grade requests from another tool's launch.
Related errors
- course_invalid
- Access token expired
- Access token invalid - signature likely incorrect
- assets_url host for '#
- assignment_invalid
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/89e9d9a1ee0dae76.
Report an issue: GitHub.
Appendix: source
Thrown at lib/basic_lti/basic_outcomes.rb:76
InstStatsd::Statsd.distributed_increment("lti.1_1.basic_outcomes.bad_requests",
tags: { error_code: "InvalidRequest" })
super
end
def response_status
415
end
end
# gives instfs about 7 hours to have an outage and eventually take the file
MAX_ATTEMPTS = 10
SOURCE_ID_REGEX = /^(\d+)-(\d+)-(\d+)-(\d+)-(\w+)$/
def self.decode_source_id(tool, sourceid)
tool.shard.activate do
sourcedid = BasicLTI::Sourcedid.load!(sourceid)
raise BasicLTI::Errors::InvalidSourceId.new("Tool is invalid", :tool_invalid) unless tool == sourcedid.tool
return sourcedid.assignment, sourcedid.user
end
end
def self.process_request(tool, xml)
InstStatsd::Statsd.time("lti.1_1.basic_outcomes.process_request_time") do
res = (quizzes_next_tool?(tool) ? BasicLTI::QuizzesNextLtiResponse : LtiResponse).new(xml)
unless res.handle_request(tool)
res.code_major = "unsupported"
res.description = "Request could not be handled. ¯\\_(ツ)_/¯"
end
res
end
end
def self.quizzes_next_tool?(tool)View on GitHub (pinned to 1c9f0bb801)