{"record":{"id":"23460987b80520c6","repo":"instructure/canvas-lms","slug":"developer-key-mismatch","errorCode":null,"errorMessage":"Developer key mismatch","messagePattern":"Developer key mismatch","errorType":"exception","errorClass":"Lti::Errors::InvalidToolProxyError","httpStatus":null,"severity":"error","filePath":"app/models/lti/tool_proxy_service.rb","lineNumber":94,"sourceCode":"      installing_vendor = tool_proxy&.tool_profile&.product_instance&.product_info&.product_family&.vendor&.code\n      return true if installing_vendor.blank?\n\n      vendor_dev_keys = DeveloperKey.by_cached_vendor_code(installing_vendor)\n      return false if developer_key.blank? && vendor_dev_keys.blank?\n\n      !vendor_dev_keys.include?(developer_key)\n    end\n\n    def deprecated_split_secret?(tp)\n      tp.enabled_capability.present? &&\n        tp.enabled_capability.include?(\"OAuth.splitSecret\") &&\n        tp.security_contract.tp_half_shared_secret.present?\n    end\n\n    def create_tool_proxy(tp:, context:, product_family:, tool_proxy: nil, registration_url:, developer_key: nil)\n      # make sure the guid never changes\n      raise Lti::Errors::InvalidToolProxyError if tool_proxy && tp.tool_proxy_guid != tool_proxy.guid\n      raise Errors::InvalidToolProxyError, \"Developer key mismatch\" if developer_key_mismatch?(tp, developer_key)\n\n      tool_proxy ||= ToolProxy.new\n      tool_proxy.registration_url = registration_url\n      tool_proxy.product_family = product_family\n      tool_proxy.guid = tp.tool_proxy_guid\n      tool_proxy.shared_secret = create_secret(tp)\n      tool_proxy.product_version = tp.tool_profile.product_instance.product_info.product_version\n      tool_proxy.lti_version = tp.tool_profile.lti_version\n      tool_proxy.name = tp.tool_profile.product_instance.product_info.default_name\n      tool_proxy.description = tp.tool_profile.product_instance.product_info.default_description\n      tool_proxy.context = context\n      tool_proxy.workflow_state ||= \"disabled\"\n      tool_proxy.raw_data = tp.as_json\n      tool_proxy.update_payload = nil\n      tool_proxy.save!\n      tool_proxy\n    end\n","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/lti/tool_proxy_service.rb#L76-L112","documentation":"Lti::ToolProxyService#create_tool_proxy raises Errors::InvalidToolProxyError with message \"Developer key mismatch\" when developer_key_mismatch?(tp, developer_key) is true — the developer key associated with the incoming tool proxy payload does not match the developer key supplied to the registration request. This prevents registering a tool proxy under the wrong key during LTI 2 registration.","triggerScenarios":"Processing tool proxy JSON (process_tool_proxy_json) where the ToolProxy's declared developer key GUID differs from the developer_key passed by the caller — e.g. the tool was registered with one key but the confirmation request carries another, or the key was rotated/changed between registration steps.","commonSituations":"Re-registering a tool after its developer key was replaced; using the wrong developer_key in the registration URL/callback; installing the same tool proxy guid against a different key on another account.","solutions":["Ensure the developer_key passed to process_tool_proxy_json matches the key bound to the tool proxy payload / registration request.","If the key was rotated, redo the tool registration from scratch so the proxy is created under the current key.","Check the stored tool_proxy record: if a proxy with the same guid already exists under another key, remove it or use the original key.","Verify the registration URL flow isn't replayed with a stale payload referencing the old key."],"exampleFix":"// before\nservice.process_tool_proxy_json(tp_json, developer_key: old_key)\n// after\ncurrent_key = DeveloperKey.find(tp_json.dig('tool_proxy', 'security_contract')&.dig('developer_key')) || expected_key\nservice.process_tool_proxy_json(tp_json, developer_key: current_key)","handlingStrategy":"try-catch","validationCode":"raise 'key mismatch' if developer_key && tp.developer_key && developer_key.global_id != tp.developer_key.global_id","typeGuard":null,"tryCatchPattern":"begin\n  service.process_tool_proxy_json(json, developer_key:)\nrescue Lti::Errors::InvalidToolProxyError => e\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Always derive the developer key from the same registration session that created the proxy","Redo full registration after key rotation instead of replaying old payloads","Check for existing tool proxies with the same guid bound to another key"],"tags":["ruby","lti","tool-proxy","authentication"],"backgroundTag":"authentication-required","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}