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 both flows using your own API credentials. Every launch call and callback 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.
  • Two 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, forming two flows.

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:

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

The resource flow

A learner opens the linked content with launchType: "resourceRequest". When they complete it, WeVideo reports the score back:

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"
}
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.

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 if the launch is intended to link a new piece of content to your platform, resourceRequest if not. Only teacher and admin level roles can go through a linkingRequest launch.
targetLinkNoThe internal link the user is redirected to after login. Should only be set for resourceRequest launches, and should be the targetLink value sent to your platform in the deep linking callback.
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. 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. 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 or launchType value are rejected with a 400 response.

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.

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.

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 resourceRequest launch.

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>"
}

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.

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.