instructure/canvas-lms · error · Lti::PlatformNotificationService::InvalidNoticeHandler
Validation failed: Notice type unknown, must be one of
Error message
Validation failed: Notice type unknown, must be one of [#{Lti::Pns::NoticeTypes::ALL.join(", ")}] What it means
validate_notice_type! checks the given notice_type against Lti::Pns::NoticeTypes::ALL before subscribing or unsubscribing, raising InvalidNoticeHandler with an explicit 'Notice type unknown' message. The model also validates this for subscribing, but this class-level check guarantees consistent errors for unsubscribe too.
Solutions
- Use a constant from Lti::Pns::NoticeTypes (e.g. NoticeTypes::HELLO_WORLD) instead of a raw string.
- Check the allowed list via Lti::Pns::NoticeTypes::ALL and match casing exactly.
- Update tool configuration/JSON to reference a currently supported notice type.
- If a new notice type is genuinely needed, add it to Lti::Pns::NoticeTypes::ALL and its builders.
Example fix
# before subscribe_tool_for_notice(tool:, notice_type: 'HelloWorld') # after subscribe_tool_for_notice(tool:, notice_type: Lti::Pns::NoticeTypes::HELLO_WORLD)
Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, "unknown notice_type" unless Lti::Pns::NoticeTypes::ALL.include?(notice_type)
Try / catch
begin
Lti::PlatformNotificationService.unsubscribe_tool_for_notice(tool:, notice_type:)
rescue Lti::PlatformNotificationService::InvalidNoticeHandler => e
render json: { error: e.message }, status: :bad_request
end Prevention
- Always use Lti::Pns::NoticeTypes constants, never raw strings
- Validate notice_type against NoticeTypes::ALL in controllers before the service call
- Update tool configurations when notice types are renamed or removed
- Handle both subscribe and unsubscribe paths with the same validation
When it happens
Trigger: Calling subscribe_tool_for_notice or unsubscribe_tool_for_notice with a notice_type string not present in Lti::Pns::NoticeTypes::ALL (typo, wrong casing, deprecated/renamed notice type).
Common situations: LTI tool configuration referencing an old or renamed notification type; typos like 'HelloWorld' vs 'hello_world'; clients hardcoding notice types instead of using the NoticeTypes constants.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- e.message
- " " must be either " " or
- invalid date type: #
- Missing required asset parameter #
- Missing required parameter: copied_at
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/ca07357b03fedaaa.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/lti/platform_notification_service.rb:105
def notify_tools(cet_id_or_ids:, builders:)
notice_type = get_notice_type(builders:)
Lti::NoticeHandler.active.where(notice_type:, context_external_tool_id: cet_id_or_ids).find_each do |notice_handler|
send_notices(notice_handler:, builders:)
end
end
def notify_asset_processor(asset_processor, *builders)
notice_type = get_notice_type(builders:)
Lti::NoticeHandler.active.where(notice_type:, context_external_tool_id: asset_processor.context_external_tool_id).find_each do |notice_handler|
send_notices(notice_handler:, builders:)
end
end
def validate_notice_type!(notice_type)
# This is also validated in the model, but we want to validate for
# unsubscribing and have a consistent error also for subscribing
unless Lti::Pns::NoticeTypes::ALL.include?(notice_type)
raise InvalidNoticeHandler, "Validation failed: Notice type unknown, must be one of [#{Lti::Pns::NoticeTypes::ALL.join(", ")}]"
end
end
private_class_method :validate_notice_type!
def send_notices(notice_handler:, builders:)
builders.each_slice(notice_handler.max_batch_size || builders.length) do |batch|
send_notice_batch(notice_handler:, builders: batch)
end
end
private_class_method :send_notices
def send_notice_batch(notice_handler:, builders:)
tool = notice_handler.context_external_tool
global_id = generate_notification_uuid
notice_objects = builders.map { |builder| builder.build(tool) }
webhook_body = { notices: notice_objects }.to_json
log_info = builders.map { |builder| builder.info_log(tool) }View on GitHub (pinned to 1c9f0bb801)