Skip to main content

Pseudo LTI flow

The Pseudo LTI flow is an API integration flow for platforms that want an LTI-like WeVideo integration but don't support LTI themselves. It mimics the deep linking and grade passback parts of the LTI 1.3 specification, but as a simplified set of JSON API calls with fewer properties and configurable settings.

Throughout this guide, platform means your product — the third-party system, owned by you, that embeds WeVideo.

Try it in your browser

The pseudo LTI demo is a dummy LMS that walks through the flows using your own API credentials. Its settings let you switch between the callback and postMessage linking modes, between opening content by target link or by content reference, and between the two learner session modes, so you can try each variant before building it. Every launch call, callback and content selected message is shown in an event log, including live signature verification. All demo data stays in your browser — nothing is stored on our side.

How it works

The whole integration consists of:

  • One API call from your servers to WeVideo — the launch call, which sets up a session and returns a launch URL.
  • Callback calls from WeVideo's servers to yours — the deep linking callback when a teacher selects content, and the grade callback when a learner completes it.
  • A client side that embeds WeVideo in an iframe, pointing at the launch URL.

All API calls are server-to-server and all payloads are JSON. The launch call is authenticated like any other WeVideo API call — see Authentication.

Callback authentication

The callback calls to your servers are signed with your own API credentials, so you can verify that they actually come from WeVideo. Verifying the signature is optional but recommended — see Verifying callbacks.

There are two launch types: linkingRequest to link a piece of content, and resourceRequest to open it. Linking comes in two variants — one server-to-server, one browser-only — and they hand you different identifiers for the content, which you pass back in the resource launch. Which identifier you send is up to you; WeVideo works out the rest.

The linking flow

A teacher launches WeVideo with launchType: "linkingRequest" to pick a piece of content (an interactive video or playlist) to link into your platform. By default WeVideo reports the selection with a server-to-server call to your deepLinkingCallbackUrl:

Store the targetLink from the deep linking callback — it's what you use to launch the selected content later.

The linking flow without a callback

If you'd rather not implement a callback endpoint, launch with linkingMode: "postMessage". WeVideo then reports the selection as a postMessage to the parent frame instead, and makes no callback call at all:

Store the contentType and contentId from the message — that's what you use to launch the selected content later. Neither deepLinkingCallbackUrl nor deepLinkingReturnUrl is needed in this mode.

The resource flow

A learner opens the linked content with launchType: "resourceRequest", and WeVideo reports the score back when they complete it. Name the content with whichever identifier you stored when it was linked:

  • the targetLink you received in the deep linking callback, or
  • the contentType and contentId you received in the content selected message.

Send one or the other — if you send both, the content reference wins. A target link that points at linked content resolves to the same content type and ID internally, so a platform holding a mix of the two doesn't need to care which it stored.

When the assignment is created

Content linked through the callback mode already has an assignment — it was created when the teacher made the selection. Content linked through the postMessage mode doesn't, since nothing was called at linking time, so the class and the assignment are created on the first resource launch instead. Either way, later launches reuse them.

The launch call

POST https://www.wevideo.com/api/5/pseudo-lti/launch

This server-to-server call sets up a new session for a user in WeVideo, creating a WeVideo user if necessary. The user is resolved like this:

  1. WeVideo first looks for an existing user mapped to the provided userId.
  2. If there's no mapping, it looks for an existing user with the provided email address. If one is found, it's mapped to the userId.
  3. Otherwise a new user is created from the email, firstName and lastName values, unless there already is another user with the same email address in the WeVideo account.

The typical usage is:

  1. An end user clicks a button in your UI to launch WeVideo.
  2. Your client-side code makes a request to your server, which makes the launch call to WeVideo and returns the received toolLaunchUrl to the client.
  3. The client opens an iframe pointing at the toolLaunchUrl.
Example request — linking a new piece of content
{
"contextId": "course-1234",
"classId": "class-5678",
"className": "Biology 101",
"userId": "platform-user-42",
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada.lovelace@example.com",
"role": "teacher",
"launchType": "linkingRequest",
"deepLinkingCallbackUrl": "https://platform.example.com/wevideo/deep-linking",
"deepLinkingReturnUrl": "https://platform.example.com/wevideo/close"
}
Example request — a learner opening linked content
{
"contextId": "course-1234",
"classId": "class-5678",
"className": "Biology 101",
"userId": "platform-user-97",
"firstName": "Grace",
"lastName": "Hopper",
"email": "grace.hopper@example.com",
"role": "student",
"launchType": "resourceRequest",
"targetLink": "<the targetLink received in the deep linking callback>",
"gradeCallbackUrl": "https://platform.example.com/wevideo/grades"
}
Example request — a learner opening linked content by content reference
{
"contextId": "course-1234",
"classId": "class-5678",
"className": "Biology 101",
"userId": "platform-user-97",
"firstName": "Grace",
"lastName": "Hopper",
"email": "grace.hopper@example.com",
"role": "student",
"launchType": "resourceRequest",
"contentType": "interactiveVideo",
"contentId": 123456,
"gradeCallbackUrl": "https://platform.example.com/wevideo/grades"
}
tip

The iframe needs allow="microphone; camera; fullscreen" for recording and fullscreen to work. See the embedding guide for details.

Testing tip

Opening the toolLaunchUrl signs the browser in to WeVideo as the launched user. If you test from a browser where you're already logged in to WeVideo, your own session is replaced — use a private window instead, or launch learners with a tab-scoped session, which leaves your own session alone.

Payload properties

PropertyRequiredDescription
contextIdYesA unique identifier for the context WeVideo is opened in. This could be the same as the classId, or it could be something else.
classIdYesA unique identifier for the class (or similar concept) WeVideo is opened from on the platform side. It's included in the callback calls, letting you map each callback to the correct class.
classNameYesThe name of the class, course or context WeVideo is opened from. It's used to create a corresponding class in WeVideo when a learner opens a linked piece of content.
userIdYesA unique identifier representing the user on the platform side. It's included in the callback calls, letting you map each callback to the correct user.
firstNameNoUsed as the first name if a new WeVideo user needs to be created. Defaults to Firstname if not provided.
lastNameNoUsed as the last name if a new WeVideo user needs to be created. Defaults to Lastname if not provided.
emailYesUsed as the email address if a new WeVideo user needs to be created.
roleYesThe role the user should have on the WeVideo side. Allowed values are student, teacher and admin. The call never "demotes" a user to a lower-level role, e.g. from admin to student.
launchTypeYeslinkingRequest to link a new piece of content to your platform, resourceRequest to open linked content. Only teacher and admin level roles can go through a linkingRequest launch.
linkingModeNoOnly used for linkingRequest launches. callback (the default) reports the selected content with the deep linking callback call; postMessage reports it as a message to the parent frame instead, and makes no callback call.
sessionAuthModeNoOnly used for learner launches. cookie (the default) signs the browser in to WeVideo with a session cookie; sessionKey scopes the session to the launched tab instead — see Learner sessions. Rejected for teacher and admin launches.
targetLinkNoFor resourceRequest launches: the targetLink value sent to your platform in the deep linking callback. One of the two ways to name the content to open.
contentTypeNoFor resourceRequest launches: the type of content to open, interactiveVideo or playlist, as sent in the content selected message. The other way to name the content — set it together with contentId.
contentIdNoFor resourceRequest launches: the WeVideo ID of the content to open, as sent in the content selected message. Set it together with contentType.
completionTypeNoOnly used for resourceRequest launches of an interactive video: regular or broadcast. Pass back the value from the content selected message so learners get the experience the teacher picked. Read from the targetLink when you name the content that way. Leaving it out keeps whatever experience the assignment already has.
dueDateNoOnly used for resourceRequest launches. An optional due date for the assignment, as an ISO 8601 string. Learners can't complete interactions or progress in the attempt after this date.
gradeCallbackUrlNoShould only be set for resourceRequest launches. The URL WeVideo makes the grade callback call to when a learner completes an assignment.
deepLinkingCallbackUrlNoShould only be set for linkingRequest launches with the default callback linking mode. The URL WeVideo makes the deep linking callback call to when a teacher selects a piece of content.
deepLinkingReturnUrlNoShould only be set for linkingRequest launches with the default callback linking mode. The URL the teacher's browser is sent to when they select a piece of content — via an auto-submitted form POST, so the endpoint must accept POST requests. Typically just a URL that closes the WeVideo iframe on the platform side.

Launch requests with an unrecognized role, launchType, linkingMode, sessionAuthMode, contentType or completionType value are rejected with a 400 response, as are launches that set only one of contentType and contentId, and launches that ask for sessionAuthMode: "sessionKey" for anyone but a learner. A resourceRequest launch that names no content at all just opens WeVideo without opening anything in particular.

Callback URLs

Query parameters in gradeCallbackUrl and deepLinkingCallbackUrl are preserved, so you can encode your own identifiers into them — for example https://platform.example.com/wevideo/grades?assignmentId=42.

Response properties

Example response
{
"toolLaunchUrl": "https://www.wevideo.com/api/5/pseudo-lti/login/<one-time-token>",
"wevideoUserId": 123456
}
PropertyDescription
toolLaunchUrlThe URL the end user should launch WeVideo from — typically used as the src attribute of an iframe on the platform side. It contains a one-time-use token that logs the user in to WeVideo and opens the app in the right context based on the launch request.
wevideoUserIdThe unique identifier of the user on the WeVideo side.

Learner sessions

By default, opening the toolLaunchUrl signs the browser in to WeVideo with a session cookie, the same way logging in to WeVideo directly does. That session belongs to the whole browser: the launched user stays signed in if they open WeVideo in another tab, and only one user can be signed in at a time.

Learner launches can ask for a session scoped to the launched tab instead:

Example request — a learner launch scoped to the tab
{
"contextId": "course-1234",
"classId": "class-5678",
"className": "Biology 101",
"userId": "platform-user-97",
"firstName": "Grace",
"lastName": "Hopper",
"email": "grace.hopper@example.com",
"role": "student",
"launchType": "resourceRequest",
"sessionAuthMode": "sessionKey",
"contentType": "interactiveVideo",
"contentId": 123456,
"gradeCallbackUrl": "https://platform.example.com/wevideo/grades"
}

With sessionAuthMode: "sessionKey", WeVideo hands the browser a session key that it keeps in the launched tab and sends with each of its own API calls, and no usable WeVideo session cookie is left behind. Nothing changes in how you make the launch call, in the toolLaunchUrl you embed, or in the callbacks you receive. Two things change for the end user:

  • The session stays in the tab. Opening WeVideo in another tab or window shows the learner as logged out — the launch no longer signs them in to the rest of WeVideo in that browser.
  • Different users can be launched at the same time. Because each tab holds its own session, you can have a learner in one tab and a teacher or admin in another, in the same browser.

Constraints

  • Learner launches only. sessionAuthMode: "sessionKey" is rejected with a 400 unless role is student. The teacher and admin flows deliberately hand the session from the iframe to a new tab, which a tab-scoped session can't do.
  • The session ends with the tab. It survives reloads, but closing the tab ends it, and a new tab needs a new launch. Sessions also expire 24 hours after the launch and can't be extended, so a learner still in the same tab after that needs a fresh launch.

The deep linking callback

When a teacher in a linkingRequest launch selects a piece of content, WeVideo makes an API call to the deepLinkingCallbackUrl provided in the launch call. This is the default way a selection is reported — launches with linkingMode: "postMessage" get a message to the parent frame instead, and no callback call is made.

Example payload
{
"contextId": "course-1234",
"classId": "class-5678",
"userId": "platform-user-42",
"type": "bulb",
"title": "My interactive video",
"targetLink": "<launch link for the selected content>"
}

Payload properties

PropertyDescription
contextIdThe context ID received from your platform in the launch call.
classIdThe class ID received from your platform in the launch call.
userIdThe user ID received from your platform in the launch call.
typeThe type of content that was selected. Either bulb (interactive video) or playlist.
titleThe title of the selected content, e.g. My interactive video.
targetLinkThe link back to WeVideo that launches the selected piece of content. This is the targetLink value your platform should provide in the launch call for a resource launch.

The content selected message

When a teacher in a linkingRequest launch with linkingMode: "postMessage" selects a piece of content, the WeVideo iframe posts a message to its parent frame — your page — instead of WeVideo calling your server. There's no callback call and no form POST to a return URL in this mode, so your page owns both storing the selection and closing the iframe.

Linking is a single click in this mode: picking content from the list reports it immediately, with no confirmation step in between. The message therefore only names the content — see the note below if you need a learner experience or a due date.

Example payload
{
type: "wevideo:contentSelected",
contentType: "interactiveVideo",
contentId: 123456,
title: "My interactive video"
}

Message properties

PropertyDescription
typeAlways the literal string wevideo:contentSelected. Check it before handling a message — your window receives messages from other sources too.
contentTypeThe type of content that was selected, interactiveVideo or playlist. Pass it back as contentType in the resource launch.
contentIdThe WeVideo ID of the selected content, as a number. Pass it back as contentId in the resource launch.
titleThe title of the selected content, e.g. My interactive video. Omitted if the content has no title.

Store the message and pass the values back in the launch call when a learner opens the content:

Receiving the message
window.addEventListener("message", event => {
// The iframe is served from your WeVideo host — check the origin before trusting the message
if (event.origin !== "https://www.wevideo.com") return;
if (event.data?.type !== "wevideo:contentSelected") return;

saveLinkedContent({
contentType: event.data.contentType,
contentId: event.data.contentId,
title: event.data.title,
});
closeWeVideoIframe();
});
Check the origin

The message is posted without a fixed target origin, since WeVideo doesn't know your page's origin. Always verify event.origin against the WeVideo host you launched, and verify event.data.type, before acting on a message.

Choosing a learner experience or due date

The callback linking mode shows the teacher a confirmation step where they can pick the learner experience (self-paced or live broadcast) and a due date. The postMessage mode has no such step — one click links the content — so neither is chosen in WeVideo.

If you need them, set them from your own side: completionType and dueDate on the resource launch apply to the assignment, so you can collect them in your own UI and pass them along with every launch. completionType defaults to regular for content that has no assignment yet, and leaving it out never changes an assignment that already exists.

The grade callback

When a learner in a resourceRequest launch completes an interactive video or playlist, WeVideo makes an API call to the gradeCallbackUrl provided in the launch call.

Example payload
{
"contextId": "course-1234",
"classId": "class-5678",
"userId": "platform-user-97",
"timestamp": "2026-07-22T14:03:22.123Z",
"scoringUserId": null,
"scoreGiven": 8.0,
"scoreMaximum": 10.0,
"targetLink": "<the targetLink of the completed resource>",
"contentType": null,
"contentId": null
}

Payload properties

PropertyDescription
contextIdThe context ID the resource attempt was completed in.
classIdThe class ID of the resource attempt.
userIdThe user ID that completed the attempt.
timestampThe time the interactive video or playlist attempt was completed, as an ISO 8601 string.
scoringUserIdNormally null. If a teacher, for example, adjusts the score for an assignment, this is the user ID of that teacher.
scoreGivenThe score the user received, as a numeric value.
scoreMaximumThe total available score, as a numeric value.
targetLinkThe targetLink of the completed resource, as provided in the launch call. Lets you tell which resource the grade belongs to when several pieces of content are linked to the same class. null if the launch didn't provide one.
contentTypeThe type of the completed resource, interactiveVideo or playlist. Set whenever WeVideo knows it — either because you sent it, or because it resolved it from your targetLink.
contentIdThe WeVideo ID of the completed resource, on the same terms as contentType.

Responding to callbacks

Your callback endpoints should respond with a 2xx status code. The response body is ignored.

  • The deep linking callback is made while the teacher waits: it must succeed before the teacher is redirected to the deepLinkingReturnUrl, so a non-2xx response fails the content selection. Keep the handler fast and defer any heavy processing.
  • The grade callback is made once per completed attempt and is not retried. If your endpoint responds with a non-2xx status, the score report is lost (the failure is logged on the WeVideo side).

Verifying callbacks

The callback calls are signed with the same HMAC scheme you use to authenticate your calls to the WeVideo API, using your own API key and secret. Each callback request includes these headers:

HeaderValue
AuthorizationWEV <your API key>:<signature>
DateThe time the request was signed

The signature is computed like this:

StringToSign = "POST" + "\n" + MD5(RequestBody) + "\n" + Date + "\n" + CallbackUrl
Signature = Base64(HMAC-SHA256(YourAPISecret, UTF-8(StringToSign)))

Where CallbackUrl is the full callback URL exactly as you provided it in the launch call (including any query string), MD5(RequestBody) is the lowercase hex MD5 of the raw request body, and Date is the value of the Date header as-is.

Keep — or deterministically rebuild — the exact callback URL string you sent in the launch call; a single differing character (encoding, parameter order, a trailing slash) makes verification fail.

To verify a callback, recompute the signature and compare it to the one in the Authorization header:

Verifying a callback signature (Python)
import base64
import hashlib
import hmac

def verify_callback(headers, raw_body: bytes, callback_url: str, api_key: str, api_secret: str) -> bool:
authorization = headers.get("Authorization", "")
if not authorization.startswith("WEV "):
return False

key, _, signature = authorization[4:].partition(":")
if key != api_key:
return False

body_md5 = hashlib.md5(raw_body).hexdigest()
string_to_sign = f"POST\n{body_md5}\n{headers['Date']}\n{callback_url}"
expected = base64.b64encode(
hmac.new(api_secret.encode(), string_to_sign.encode(), hashlib.sha256).digest()
).decode()

return hmac.compare_digest(signature, expected)

If the signature is missing or doesn't verify, treat the callback as untrusted. You can also reject requests whose Date header is more than a few minutes old to guard against replays.