Events API (postMessage)
When a VCMS component is embedded via iframe into your platform, the component can forward user interactions (clicking "More", clicking a video) to the parent page using the browser-native window.postMessage, letting your platform decide how to handle them.
Cross-origin safe: an iframe and its parent can communicate even on different origins — this is the standard use of postMessage.
Two modes
Enable by adding the events parameter to the embed URL:
| Mode | URL param | Behavior |
|---|---|---|
| Emit only | ?events=1 | The component does not navigate; it only emits events to the parent. Your platform handles them (open a player in a new window, use your own router, show a modal, etc.). |
| Navigate + emit | ?events=nav | The component navigates to the portal itself (full page) and emits the event to you (for analytics). |
| Disabled | (no events) | Default: the component navigates itself with target="_top" and emits nothing. |
events=navwaits about 120ms after emitting before navigating, so your listener (e.g. analytics) runs first.
Embed examples
<!-- Emit only: your platform fully controls the interaction -->
<iframe src="https://vcms-qa.acorners.com/strip?platform=1&events=1"
width="100%" height="300" frameborder="0" scrolling="no"
allow="autoplay;encrypted-media;picture-in-picture"></iframe>
<!-- Navigate + emit: the component navigates to the portal itself; you only track -->
<iframe src="https://vcms-qa.acorners.com/strip?platform=1&events=nav"
width="100%" height="300" frameborder="0" scrolling="no"
allow="autoplay;encrypted-media;picture-in-picture"></iframe>
The events parameter works on the strip /strip, the full page /embed, and — since
v1.4.1 — the watch page /watch/<videoId>?full=1.
The watch page is an event source too (since v1.4.1)
When you embed the full watch page, the video cards under 「视频分类」 and the "More videos" link emit events as well:
<div style="position:relative;width:100%;padding-top:56.25%">
<iframe id="vcms-watch"
src="https://vcms-qa.acorners.com/watch/{videoId}?full=1&platform=1&events=1"
style="position:absolute;inset:0;width:100%;height:100%"
frameborder="0" allow="autoplay;encrypted-media;picture-in-picture"
allowfullscreen></iframe>
</div>
⚠️ One key difference from the strip and the full page
The watch page navigates inside its own iframe. Click a video in one of its sections and the frame simply swaps to that video and keeps playing. Your page does not move.
So its events are notifications, not requests to navigate. Copying the standard listener
below verbatim (window.open(e.data.watchUrl)) makes a single click happen twice — the frame
swaps video by itself, and your page also opens a tab.
| Component | On click | What you should do |
|---|---|---|
Strip /strip, full page /embed | events=1 does not navigate; events=nav navigates your top window | Handle watchUrl / url per mode |
Watch page /watch | Always navigates inside the iframe | Record only — do not navigate again |
Telling several embeds apart
The payload carries no "which component sent this" field, and needs none — you already hold a
reference to each iframe, and MessageEvent.source is the window that posted:
<script>
const watchFrame = document.getElementById('vcms-watch');
window.addEventListener('message', (e) => {
if (!e.data || e.data.source !== 'vcms') return;
if (watchFrame && e.source === watchFrame.contentWindow) {
// The watch page already navigated inside the frame — just record it.
// Replace with your own analytics call:
console.log('vcms', e.data.type, e.data);
return;
}
// Strip / full page: handle according to the mode you chose
if (e.data.type === 'video:click') window.open(e.data.watchUrl, '_blank');
if (e.data.type === 'more:click') location.href = e.data.url;
});
</script>
This scales to any number of embeds and does not depend on us adding a field to the payload.
Two behavioural details
- Every click emits, not just the first. When the watch page navigates inside the frame it
carries the
eventsparameter along to the next video, so you will not see "one event and then silence". - Events stop after "More videos". That link leaves the watch page and lands on the video
centre home
/inside the frame, and the home page is not an event source (/embedis). This is a deliberate trade-off, not a fault — tell us if you need events to continue past it.
Language switching (i18n)
The components support 7 languages: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), en, ms (Malay), th (Thai), id (Indonesian), vi (Vietnamese). Both the UI labels and the video content (title, description, category name, brand name — where a translation has been configured in the admin) switch accordingly; languages without a translation fall back to Simplified Chinese.
① Initial language — the lang URL param
<iframe src="https://vcms-qa.acorners.com/strip?platform=1&lang=en"></iframe>
② Runtime switching — parent postMessage (no iframe reload)
When your platform changes language, post a vcms-host message to the iframe. The component switches its UI and re-fetches content in that language immediately, without reloading the iframe:
const frame = document.querySelector('iframe');
frame.contentWindow.postMessage(
{ source: 'vcms-host', type: 'setLang', lang: 'th' },
'*',
);
| Field | Value |
|---|---|
source | Always 'vcms-host' (distinct from the 'vcms' the component sends you) |
type | 'setLang' |
lang | One of the 7 language codes above |
Message format
Every event is an object sent via window.parent.postMessage(payload, '*'). All events carry source: 'vcms' — use it to filter so you don't confuse them with other message events on the page.
interface VcmsEvent {
source: 'vcms';
type: 'video:click' | 'more:click';
// remaining fields depend on type, see tables below
[key: string]: unknown;
}
Event list
video:click — the user clicked a video
| Field | Type | Notes |
|---|---|---|
source | 'vcms' | Fixed value |
type | 'video:click' | Event type |
videoId | string | Video ID |
title | string | Video title |
watchUrl | string | Full URL of that video's player page (autoplays when opened). ⚠️ If the event came from the watch page, the frame has already gone there — treat this as informational and do not open it again |
{
"source": "vcms",
"type": "video:click",
"videoId": "dcfc79f8-5650-4f6f-9416-3161e33f7af6",
"title": "APB Brand Trailer",
"watchUrl": "https://vcms-qa.acorners.com/watch/dcfc79f8-5650-4f6f-9416-3161e33f7af6"
}
more:click — the user clicked "More"
| Field | Type | Notes |
|---|---|---|
source | 'vcms' | Fixed value |
type | 'more:click' | Event type |
url | string | Full URL of the video center "All" page |
{
"source": "vcms",
"type": "more:click",
"url": "https://vcms-qa.acorners.com/"
}
Integration (listening on the parent page)
On the page that embeds the iframe, add a message listener and switch on type:
<script>
window.addEventListener('message', (e) => {
// 1) Only accept VCMS events
if (!e.data || e.data.source !== 'vcms') return;
// 2) (Recommended) verify the origin for extra safety
// if (e.origin !== 'https://vcms-qa.acorners.com') return;
switch (e.data.type) {
case 'video:click':
// e.g. open the player in a new window
window.open(e.data.watchUrl, '_blank');
// or: record analytics
// track('video_click', { id: e.data.videoId, title: e.data.title });
break;
case 'more:click':
// e.g. navigate to the video center
location.href = e.data.url;
break;
}
});
</script>
The snippet above is for the strip and the full page. If you also embed the watch page, use the version under "Telling several embeds apart" above instead — otherwise a click in the watch page navigates twice.
Which mode should I use?
- Want to control what happens after a click yourself (open a new window, route inside your SPA, show a confirm dialog first) → use
events=1and handlewatchUrl/urlin your listener. - Just want to record what the user clicked and let the component navigate → use
events=nav; your listener only tracks, the component navigates itself. - Embedding the watch page → still pass
events=1, but it always navigates inside the frame; your listener should only track.
User identifier (uid)
Like lang, uid is a URL parameter, not something sent via postMessage:
<iframe src="https://vcms-qa.acorners.com/strip?platform=1&uid=a3f8c91e4b7d2065f1c8e93a4d6b0271"></iframe>
With it, likes / dislikes and search history are kept per that user instead of per browser. It
only takes effect together with platform; without it, behaviour is exactly as before. Full rules in
Overview · optional parameter uid.
uidis never echoed back to you inpostMessageevents — you supplied the value in the first place.
Security & notes
- ⚠️
uidis not an authentication credential: it is a plain-text URL parameter that anyone can edit. Do not use it to carry a login state. Forging it can at most affect that user's own like state and search history, but you should never treat it as proof of identity. - Filter on
source: always checke.data.source === 'vcms'first — the page may have otherpostMessagetraffic. - Verify
e.origin(recommended): in production, add a check likee.origin === 'your VCMS portal domain'to prevent spoofing. - Hotlink protection: your domain must be on the allowed-referrer whitelist (contact us to enable it), otherwise videos/thumbnails return 403.
- Autoplay:
watchUrlattempts to autoplay when opened; the browser's autoplay policy may require muting or a prior user interaction — this is a browser-level constraint. targetOrigin: the component sends with'*', so the receiver must validatee.data.source(and optionallye.origin).
Live demo
The "Teaching" page in the VCMS admin has a live simulation at the bottom under "Event integration": it embeds an events=1 component, and clicking "More" or a video shows the received event JSON in real time on the right — along with copy-paste embed code and listener code.
Below it (since v1.4.1) sits a second live simulation of the watch page: clicking a video in its category sections adds to the same log — a direct way to see one listener handling both components, and to watch the page navigate inside its own frame.
The same page also has a "Parent page language switching" live simulation: clicking a language button posts a setLang message to the embedded component (no reload), with copy-paste ?lang= embed code and switch code.