Skip to main content

Percus SmartEmbed SDK

The Percus SmartEmbed SDK (percus-embed-sdk) is the client-side library used to embed the Percus Player into any web page. It handles iframe creation, the postMessage communication protocol, and exposes a clean JavaScript API for controlling playback.

Installation

Via <script> tag (IIFE)

<script src="https://cdn.percus.example/percus-embed.js"></script>

The SDK is exposed as window.PercusEmbed.

Via npm / bundler (ESM)

npm install @percus/percus-embed-sdk
import { PercusEmbed } from "@percus/percus-embed-sdk";

Build outputs

FileFormatUse case
dist/percus-embed.jsIIFEPlain <script> tag
dist/percus-embed.esm.jsESMWebpack, Vite, Rollup
dist/percus-embed.d.tsTypeScript declarationsType-safe usage

Quick start

<div id="player-host" style="width: 800px; height: 450px;"></div>

<script src="./dist/percus-embed.js"></script>
<script>
const controller = PercusEmbed.init({
target: "#player-host",
playerUrl: "https://player.percus.example/runtime",
templateUrl: "https://cdn.example.com/template.json",
manifestUrl: "https://cdn.example.com/manifest.json",
data: { userId: "abc123", name: "María" },

onReady: (msg) => console.log("Player ready", msg),
onProgress: (msg) => console.log("Time:", msg.payload.time),
onError: (msg) => console.error("Error:", msg.payload.message),
});

// Control playback
controller.play();
controller.pause();
controller.seek(30); // seek to 30 seconds
controller.destroy(); // tear down
</script>

API Reference

initEmbed(params) / PercusEmbed.init(params)

Creates and injects an <iframe>, then initializes communication with the Player Runtime.

function initEmbed(params: EmbedInitParams): EmbedController

Returns an EmbedController.

EmbedInitParams

PropertyTypeRequiredDescription
targetstring | HTMLElementYesCSS selector or DOM element that will contain the iframe.
playerUrlstringNoURL of the Percus Player runtime (defaults to "about:blank").
templateUrlstringNoURL of the Lottie JSON animation template.
manifestUrlstringNoURL of the binding manifest JSON.
dataunknownNoInline personalization data object. Mutually exclusive with dataUrl.
dataUrlstringNoURL to fetch personalization data from. Mutually exclusive with data.
optionsRecord<string, unknown>NoAdditional configuration forwarded to the Player Runtime.
onReady(event: PercusReadyMessage) => voidNoCallback fired once the player finishes loading.
onProgress(event: PercusProgressMessage) => voidNoCallback fired every ~500 ms during playback.
onError(event: PercusErrorMessage) => voidNoCallback fired when the player encounters an error.

EmbedController

The object returned by initEmbed().

MemberSignatureDescription
play()() => voidSends PERCUS/PLAY to the player iframe.
pause()() => voidSends PERCUS/PAUSE to the player iframe.
seek(seconds)(seconds: number) => voidSeeks to seconds into the animation.
destroy()() => voidRemoves the iframe, unregisters all event listeners, and frees resources.
iframeHTMLIFrameElementDirect reference to the injected iframe element.

Time units: seek() accepts seconds. The SDK converts to milliseconds before forwarding to the Player Runtime.


Events (Player → Host callbacks)

onReady

Fired once after the player has loaded the template and applied bindings.

{
version: 1;
type: "PERCUS/READY";
payload: {
playerVersion?: string; // e.g. "0.1.0"
};
}

onProgress

Fired approximately every 500 ms during active playback.

{
version: 1;
type: "PERCUS/PROGRESS";
payload: {
time: number; // Current position in seconds
duration?: number; // Total duration in seconds (if known)
state?: "playing" | "paused" | "ended"; // Current playback state
};
}

onError

Fired when the player encounters an unrecoverable error.

{
version: 1;
type: "PERCUS/ERROR";
payload: {
code: string; // Machine-readable code (e.g. "LOAD_FAILED")
message: string; // Human-readable description
details?: unknown; // Optional structured context
};
}

Message contract helpers

The SDK exports utility functions used internally (and available for advanced use cases):

import {
isPercusMessage,
makeInitMessage,
makePlayMessage,
makePauseMessage,
makeSeekMessage,
} from "@percus/percus-embed-sdk";

isPercusMessage(value) // type-guard: true if value is a valid PercusMessage
makeInitMessage(payload) // build a PERCUS/INIT envelope
makePlayMessage() // build a PERCUS/PLAY envelope
makePauseMessage() // build a PERCUS/PAUSE envelope
makeSeekMessage(time) // build a PERCUS/SEEK envelope (time in seconds)

The protocol version constant is exported as:

PERCUS_MESSAGE_VERSION = 1

Advanced configuration

options (passed through EmbedInitParams.options) supports additional internal keys:

KeyTypeDefaultDescription
postMessageTargetOriginstring"*"Target origin for outbound postMessage calls. Pin this to the player domain in production.
iframe.allowstring"autoplay; fullscreen"Value for the iframe allow attribute.
iframe.referrerPolicystringReferrer policy for the iframe.
iframe.titlestring"Percus Player"Accessible title for the iframe element.
iframe.classNamestringCSS class added to the iframe element.
iframe.styleCSSStyleDeclarationborder:0; width:100%; height:100%Inline styles applied to the iframe.

Iframe defaults

When the SDK injects the <iframe>, it sets:

<iframe
src="{playerUrl}"
title="Percus Player"
allow="autoplay; fullscreen"
style="border: 0; width: 100%; height: 100%;"
></iframe>

The iframe is appended as the only child of the target element.


Lifecycle

initEmbed(params)
└── Create <iframe src="playerUrl">
└── Append to target element
└── Register window "message" listener
└── On iframe "load"
└── postMessage PERCUS/INIT → player
└── On PERCUS/READY → call onReady(event)
└── On PERCUS/PROGRESS → call onProgress(event)
└── On PERCUS/ERROR → call onError(event)

controller.play() → postMessage PERCUS/PLAY
controller.pause() → postMessage PERCUS/PAUSE
controller.seek(s) → postMessage PERCUS/SEEK { time: s }
controller.destroy()
└── Remove <iframe> from DOM
└── Remove "message" listener

TypeScript support

The SDK ships full TypeScript declarations. All message types, payload shapes, and function signatures are exported:

import type {
EmbedInitParams,
EmbedController,
PercusReadyMessage,
PercusProgressMessage,
PercusErrorMessage,
PercusInitMessage,
PercusPlayMessage,
PercusPauseMessage,
PercusSeekMessage,
PercusMessageType,
} from "@percus/percus-embed-sdk";

Planned features

The following capabilities are not yet implemented. The proposed API shapes below are provisional and intended to guide design discussions.

Analytics and tracking

A tracking configuration block will be added to EmbedInitParams. When enabled, the SDK collects player events and forwards them to a configurable backend without requiring any additional code on the host page.

// Proposed addition to EmbedInitParams
tracking?: {
enabled: boolean;
trackerGroupKey?: string; // Groups events across sessions/campaigns
googleAnalytics?: {
enabled: boolean;
eventName?: string; // Custom GA event name (default: "percus_player")
};
endpoint?: string; // Custom analytics ingestion URL
};

A trackerKey will be generated per session and included in all outbound analytics events for correlation.

Tracking will be gated behind user consent. Two new callbacks will be added:

// Proposed additions to EmbedInitParams
consentRequired?: boolean; // Default: false
onConsentAccepted?: (event: PercusConsentMessage) => void;
onConsentDeclined?: (event: PercusConsentMessage) => void;

When consentRequired is true, the SDK delays all tracking activity until onConsentAccepted fires. This supports GDPR, Law 19.628 (Chile), and LGPD (Brazil) requirements applicable to Percus clients.

Completion callbacks

Two new callbacks will fire at the end of playback:

// Proposed additions to EmbedInitParams
onPlayComplete?: (event: PercusPlayCompleteMessage) => void; // Viewer watched to the end
onPlayIncomplete?: (event: PercusPlayIncompleteMessage) => void; // Viewer left early

The onPlayIncomplete payload will include how far the viewer got (time and duration) to enable partial-watch scoring.

Call-to-action callback

// Proposed addition to EmbedInitParams
onCTA?: (event: PercusCTAMessage) => void;

Fired whenever the animation reaches a CTA marker. The host page handles the action (open a modal, redirect, log a conversion) without needing to know how CTAs are defined in the template.

Additional event callbacks

onChapterEnter?: (event: PercusChapterMessage) => void;
onChapterExit?: (event: PercusChapterMessage) => void;
onEvent?: (event: PercusEventMessage) => void; // Generic in-video event
onAutoplayFailure?: (event: PercusAutoplayFailureMessage) => void;

Playback options at init time

// Proposed additions to EmbedInitParams
autoplay?: boolean; // Attempt autoplay on ready (default: false)
mute?: boolean; // Start muted — required for autoplay in most browsers
loop?: boolean; // Loop the animation (default: false)
aspectRatio?: string; // e.g. "16x9", "9x16", "1x1" — SDK handles all responsive CSS
// Proposed addition to EmbedInitParams
modal?: {
enabled: boolean;
autoOpenTime?: number; // Milliseconds before the modal opens automatically (0 = manual)
};

When modal.enabled is true, the SDK renders a full-screen overlay with a close button instead of embedding the player inline.

Multiple player instances

A PercusEmbed.get(id) / PercusEmbed.getAll() registry API will allow the host page to retrieve and control any previously initialised player by a custom id passed at init time.

// Proposed
const c1 = PercusEmbed.init({ id: "hero-player", target: "#hero", ... });
const c2 = PercusEmbed.init({ id: "sidebar-player", target: "#sidebar", ... });

PercusEmbed.get("hero-player").pause();
PercusEmbed.getAll().forEach(c => c.destroy());

Security considerations

ConcernRecommendation
postMessageTargetOriginPin to the actual player domain (e.g. "https://player.percus.example") instead of "*".
Origin validationThe SDK validates that incoming messages come from the injected iframe's contentWindow.
Iframe sandboxAdd restrictive sandbox attributes on the container if the player origin is not fully trusted.
PII in dataPersonalization data is never stored by the SDK — it is forwarded once to the player at init time.

Examples

ESM / TypeScript with full type safety

import { PercusEmbed } from "@percus/percus-embed-sdk";
import type { PercusProgressMessage } from "@percus/percus-embed-sdk";

const controller = PercusEmbed.init({
target: document.getElementById("player-host")!,
playerUrl: "https://player.percus.example/runtime",
templateUrl: "https://cdn.example.com/pension-statement.json",
manifestUrl: "https://cdn.example.com/pension-statement.manifest.json",
dataUrl: "https://api.example.com/personalization?id=abc123",

onReady: () => controller.play(),

onProgress: (msg: PercusProgressMessage) => {
const pct = msg.payload.duration
? (msg.payload.time / msg.payload.duration) * 100
: 0;
progressBar.style.width = `${pct}%`;
},

onError: (msg) => {
console.error(`[${msg.payload.code}] ${msg.payload.message}`);
},
});

Seeking on a custom button click

document.getElementById("skip-btn").addEventListener("click", () => {
controller.seek(60); // jump to 1:00
});

Cleaning up on SPA navigation

router.on("beforeLeave", () => {
controller.destroy();
});