How LTI® Deep Linking Works
The short answer
Deep linking is a second kind of launch. Instead of opening your tool for a learner, the platform opens it for a teacher who is building a course, so they can pick something. Your tool sends back a signed response describing what they chose, and the LMS turns it into an activity that launches straight to that content.
It is specified as Deep Linking 2.0 by 1EdTech Consortium, Inc.
Without it, a teacher has to paste URLs by hand and know what to paste.
The flow
Five steps. The first and last are what make it different from a normal launch.
1. The platform sends a deep linking launch. Same OIDC handshake as any
launch, but the ID Token has
message_type: "LtiDeepLinkingRequest" and carries a deep linking settings claim:
{
"deep_link_return_url": "https://lms.example.edu/lti/deep_link_return",
"accept_types": ["ltiResourceLink", "link"],
"accept_multiple": true,
"accept_presentation_document_targets": ["iframe", "window"],
"data": "opaque-value-you-must-echo-back"
}
There is no resource link on this launch, because the point is to create one.
2. Your tool shows a picker. Ordinary application code. The only constraint is
that what you offer has to be within accept_types.
3. The teacher chooses. You build content items describing the selection:
{
"type": "ltiResourceLink",
"url": "https://your.ltiaas.com/lti/launch?resource=123",
"title": "Chapter 4 quiz"
}
For an ltiResourceLink, the url is where a future launch will go. It must be
your launch URL, with whatever parameters identify the content — not a direct
link into your application. Getting this wrong produces an activity that appears
correctly and fails when clicked.
4. Your tool signs a response. The content items go into a JWT with
message_type: "LtiDeepLinkingResponse", signed with your private key, echoing
the data claim back unchanged. Omitting data is a common cause of a rejected
response.
5. The response is form-POSTed back to deep_link_return_url, through the
browser. The platform verifies the signature and creates the activity.
The content item types
| Type | What it creates |
|---|---|
ltiResourceLink | An activity that performs a full LTI® launch into your tool. The one you almost always want. |
link | A plain hyperlink to a URL. No launch, no identity. |
file | A file the platform downloads and stores. |
html | A fragment embedded directly into the page. |
Check accept_types before returning any of them. A platform that asked for
ltiResourceLink only will reject a file, and the failure surfaces as a generic
rejection rather than a helpful message.
Why step 5 is fragile
The response goes back through the frame your tool was launched in. That is what makes deep linking the part of LTI® most easily broken by everything else.
If you open your tool in a new tab to escape third-party cookie restrictions, the frame is gone and there is nothing to submit to. The teacher picks their content, your tool believes it succeeded, and the LMS receives nothing.
The usual fix is the simplest one: do not require a login for deep linking. Content selection often does not need a session at all — read the launch, show the picker, submit the response, all inside the iframe. If it genuinely does need one, launching outside the iframe covers keeping the iframe alive and completing the submission from it.
Things that go wrong
The url points at your application, not your launch URL. The activity is
created and fails on first click, usually days later when a student finds it.
The data claim is not echoed back. Rejected, often without a clear reason.
Multiple items returned when accept_multiple is false. Rejected.
The response expires. Deep linking responses are short-lived by design. A picker that leaves a teacher browsing for twenty minutes before submitting can run out of time — mint the response at submission, not at launch.
It works in one LMS and not another. Platforms differ in which types they
accept and which extensions they honour. Read deep_linking_settings on each
launch rather than assuming.
How LTIAAS handles it
Your application receives the deep linking launch like any other and reads it with the ID Token API. When the teacher has chosen, you POST the content items to the deep linking endpoint:
const { message, target } = await axios.post(
'https://your.ltiaas.com/api/deeplinking',
{ contentItems: [{ type: 'ltiResourceLink', url: launchUrl, title: 'Chapter 4 quiz' }] },
{ headers: { Authorization: `LTIK-AUTH-V2 ${API_KEY}:${ltik}` } }
).then(r => r.data)
LTIAAS builds and signs the response with the right key, echoes data back, and
filters your content items against that platform's accept_types and
accept_multiple rather than letting a rejection happen at the LMS. You get back
a signed message and the target to post it to — or, from
/api/deeplinking/form, a ready-to-submit HTML form if you would rather not build
one.
The worked example is in the deep linking flow guide.
Common questions
What is LTI® deep linking?
A second kind of launch where the platform opens your tool so a teacher can choose something, and your tool sends a signed response back describing what they chose. The LMS then creates an activity that launches straight to it.
How is a deep linking launch different from a normal one?
The message type is LtiDeepLinkingRequest rather than LtiResourceLinkRequest, and it carries a deep linking settings claim saying what content the platform will accept and where to return the answer. There is no resource link, because the point is to create one.
Why does deep linking break when I open my tool in a new tab?
The response is submitted back through the frame the tool was launched in. Leave that frame and there is nothing to submit to, so the teacher makes a selection and the LMS never receives it.
Can I return more than one item?
Only if the platform allows it. The deep linking settings claim carries an accept_multiple flag, and returning several items to a platform that did not ask for them will be rejected.
Next
- How LTI® grade passback works
- Launching outside the iframe — and not breaking this
- Deep linking flow guide — the LTIAAS walkthrough
