# Embed the Genuin Web SDK on your website

Render your brand, community, and group feeds on your own pages with Web SDK 2.0.6, from one script tag to several embeds, sign-in, page context, and events.

The Genuin Web SDK is a script that turns a `<div>` on your page into a video embed. You configure the embed in Brand Control Center, which feed it shows and how it's laid out, and the SDK fetches that configuration by its **embed ID** and renders it. Saving a change in Brand Control Center updates every page that shows the embed at its next load.

This page covers embeds, where you pick the feed once. A **placement** chooses its videos at load time from the page a visitor is on; see [Add a Genuin placement to your website](https://begenuin.com/developers/developer-resources/sdk/how-do-you-configure-the-genuin-web-sdk-placement-on-your-website). Both use the same script and the same `init` call.

## What the SDK renders

An embed combines a feed source with a style, both set in Brand Control Center under **Grow** > **On Site** > **Embed**.

| Part | Choices | What visitors see |
| --- | --- | --- |
| Feed source | Brand Feed, Community Feed, Group Feed | Everything published across your brand, or the communities or groups you pick |
| Style | Standard Wall, Feed, Carousel | A grid of video tiles with navigation arrows, a vertical feed with a headline and button, or a horizontal row to swipe through |
| Expand view | On or off per embed | Tapping a tile opens the full-screen player with comments, reactions, and sharing over your page |

![The embed builder with the Embed Style dropdown open, listing Standard Wall, Feed, and Carousel.](https://cdn.sanity.io/images/lnrtvsvr/development/f4225e52c3cd15e7ffab739ab7ab2360eacdac21-1860x913.jpg)
*The three embed styles the SDK renders, chosen in the embed builder.*

The SDK renders inside a Shadow DOM, so its styles never leak into your page and your CSS never reaches the player. It shows a skeleton at once, loads the player when the container comes within 200 px of the viewport, and sets the container's height to fit the content.

## Requirements

| Requirement | Detail |
| --- | --- |
| Browser | ES2020 JavaScript with dynamic import(), Shadow DOM, and IntersectionObserver. Genuin tests Chrome 80, Firefox 75, Safari 13, and Edge 80 and later. |
| HTTPS | Every SDK asset and API endpoint is served over HTTPS only. Serve the host page over HTTPS as well. |
| Content Security Policy | If your page sets one, allow script-src and style-src for https://media.begenuin.com, connect-src for https://api.begenuin.com, https://media.begenuin.com, and https://rudderstack.begenuin.com, img-src and media-src for https://media.begenuin.com, style-src for https://fonts.googleapis.com, font-src for https://fonts.gstatic.com, and script-src for https://cdn.rudderlabs.com and https://cdn.jsdelivr.net. Brands that run ads add their ad vendors' hosts. |
| Container | A block element with a non-zero width and height. The SDK treats a container with zero width or height as a hidden expand-only host and renders nothing inline. |
| Credentials | An embed ID and your brand's API key, both from Brand Control Center. The key is a browser key and ships in your page source. |
| One brand per page | Every container on a page must use embeds of the same brand. Sign-in uses the first embed's brand for the whole page. |

## Step 1: Copy the embed ID and API key

1. In Brand Control Center, go to **Grow** > **On Site** and click **Embed**.
2. On the embed's card, click the **⋯** menu and choose **Copy Embed ID**.
   ![The embed list with a card's menu open showing Edit Embed, Copy Embed ID, and Delete Embed, beside the analytics panel with Unique users, Embed Impressions, and Video impressions.](https://cdn.sanity.io/images/lnrtvsvr/development/1e8f108ba486c0f60f610cf1b6be5145df09c56f-1873x909.jpg)
   *Copy Embed ID puts the id on the clipboard; the same menu edits or deletes the embed.*
3. Open the embed and click **View Embed Code**. The **Web** tab shows your API key in the generated `init` call. Copy the key from there.

To create the embed first, see [Embed your community on your website](https://begenuin.com/developers/browse-by-products/onsite/can-your-website-become-community-hub-without-building-a-new-platform).

## Step 2: Add the container and the script

Add a container where the embed goes, then load the SDK from the versioned URL. The container needs an `id` of `gen-sdk`, an `id` that starts with `gen-sdk-`, or the class `gen-sdk-class`.

```html title="index.html"
<div id="gen-sdk" class="gen-sdk-class" style="width: 100%; height: 600px;"></div>
<script src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"></script>
```

The script is a 6 KB loader. It imports the SDK bundle and its stylesheet from the same `/sdk/2.0.6/` directory and queues any `init` call you make before the bundle arrives.

> **Caution** Pin the version in the URL. The unversioned https://media.begenuin.com/sdk/gen_sdk.min.js serves the 1.2 line, which has a different API, and a floating URL changes behaviour without a deploy on your side.

## Step 3: Initialize the embed

Call `window.genuin.init` once, after the script tag. You can call it before the bundle has loaded; the loader runs it when ready.

```html title="index.html"
<script>
  window.genuin.init({
    embed_id: "{{EMBED_ID}}",
    api_key: "<<YOUR_API_KEY>>",
  });
</script>
```

The SDK finds every container on the page, fetches your brand with the API key, fetches the embed, and renders it. A container's `data-*` attributes override the matching `init` value, which is how one `init` call serves several embeds.

> **Note** init scans the DOM when it runs. In a framework, call it after the container has rendered; the SDK skips containers it has already initialized, so calling it again is safe.

## Step 4: Show several embeds on one page

Give each container a unique `id`, put its embed ID on the element, and call `init` once. `api_key` in `init` is the fallback for containers without their own `data-api-key`.

```html title="Several embeds"
<div id="gen-sdk-1" class="gen-sdk-class" data-embed-id="{{EMBED_ID_1}}"
     style="width: 100%; height: 600px;"></div>
<div id="gen-sdk-2" class="gen-sdk-class" data-embed-id="{{EMBED_ID_2}}"
     style="width: 100%; height: 400px;"></div>
<script src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"></script>
<script>
  window.genuin.init({ api_key: "<<YOUR_API_KEY>>" });
</script>
```

Two containers with the same `id` log `[Genuin SDK Warning]: Found 2 elements with the same ID` and only the first initializes.

## Step 5: Sign viewers in (Optional)

Visitors can watch without an account. To let them react, comment, and follow as themselves, pass an auto-login `token` that your server issued for the signed-in user. The SDK exchanges it for a Genuin session, creating the account on first use from `params`.

```js title="Sign in at load"
window.genuin.init({
  embed_id: "{{EMBED_ID}}",
  api_key: "<<YOUR_API_KEY>>",
  token: "{{AUTO_LOGIN_TOKEN}}",
  params: { name: "Ada Lovelace", email: "ada@example.com", nickname: "ada" },
});
```

When the visitor signs in later, call `update` with the token; it signs them into every embed on the page. Call `logout` when they sign out of your site.

```js title="Sign in later"
window.genuin.update({ container_id: "gen-sdk", token: "{{AUTO_LOGIN_TOKEN}}", user_params: {} });
window.genuin.logout();
```

To run your own login screen when a signed-out visitor taps an action, define `window.genuinAuth` before `init`. The SDK calls it instead of showing its own sign-in, with the `action` the visitor attempted and `returnQueryParams` to restore it after login.

```js title="Your login screen"
window.genuinAuth = ({ action, returnQueryParams }) => {
  // Open your login flow, then hand the token to the SDK:
  // window.genuin.update({ container_id: "gen-sdk", token: "{{AUTO_LOGIN_TOKEN}}", user_params: {} });
};
```

An invalid or expired token leaves the visitor signed out; the SDK logs a warning and renders the embed anyway. How tokens are issued is covered in [How Auto Login works](https://begenuin.com/developers/developer-resources/sdk/genuin-auto-login) and [Choose how members sign in to the Genuin SDK](https://begenuin.com/developers/developer-resources/sdk/user-authentication-mechanisms-in-genuin-sdk).

## Step 6: Pass page context (Optional)

An embed with no fixed community or group ranks its videos toward the `page_context` you send. Pass it in `contextual_params`, and change it with `update` when a single-page app changes route; the embed reloads with the merged context.

```js title="Page context"
window.genuin.init({
  embed_id: "{{EMBED_ID}}",
  api_key: "<<YOUR_API_KEY>>",
  contextual_params: {
    page_context: "electric SUVs, charging, range",
    geo: { lat: 37.7749, long: -122.4194 },
  },
});
// Later, on a route change:
window.genuin.update({ container_id: "gen-sdk", contextual_params: { page_context: "hybrid sedans" } });
```

On a container, the same values are `data-page-context`, `data-lat`, and `data-long`. The full list of signals and how they rank the feed are in [Deliver contextual feeds with page, user, and location context](https://begenuin.com/developers/developer-resources/sdk/contextual-embeds-how-they-work-and-how-to-configure-them) and [How contextual feeds choose videos](https://begenuin.com/developers/developer-resources/sdk/how-do-i-configure-and-display-contextual-feeds-as-onsite-embeds).

## Step 7: Open a specific video (Optional)

To land a visitor on one video, pass its slug. By default this opens the expand view on load; set `data-expand-on-load="false"` to play it inline instead.

```html title="Open a video"
<div id="gen-sdk" class="gen-sdk-class"
     data-embed-id="{{EMBED_ID}}" data-api-key="<<YOUR_API_KEY>>"
     data-start-video-slug="{{VIDEO_SLUG}}"
     style="width: 100%; height: 600px;"></div>
```

Add `data-action="comment"` to open the comment box as well, or `data-comment-id` to focus one comment. From code, `window.genuin.expand("gen-sdk")` and `window.genuin.collapse("gen-sdk")` open and close the expand view of a container.

## Use the SDK in a framework

The SDK has no npm package; every framework loads the same script. The pattern is the same everywhere: render the container, load the script once, and call `init` after it loads.

### React

Add a type declaration once so `window.genuin` compiles.

```ts title="genuin.d.ts"
interface Window {
  genuin?: {
    init: (config: Record<string, unknown>) => Promise<void>;
    update: (config: Record<string, unknown>) => Promise<void>;
    on: (event: string, listener: (e: { type: string; payload: unknown }) => void) => Promise<() => void>;
    logout: () => Promise<void>;
  };
}
```

Then load the script in an effect. The guard keeps React strict mode and re-mounts from adding it twice.

```tsx title="GenuinEmbed.tsx"
import { useEffect } from "react";

const SDK_URL = "https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js";

export function GenuinEmbed({ embedId }: { embedId: string }) {
  useEffect(() => {
    if (document.querySelector(`script[src="${SDK_URL}"]`)) {
      window.genuin?.init({});
      return;
    }
    const script = document.createElement("script");
    script.src = SDK_URL;
    script.async = true;
    script.onload = () => window.genuin?.init({});
    document.body.appendChild(script);
  }, []);

  return (
    <div id="gen-sdk" className="gen-sdk-class" data-embed-id={embedId}
         data-api-key="<<YOUR_API_KEY>>" style={{ width: "100%", height: 600 }} />
  );
}
```

### Next.js

In the App Router, render the container and the script from a client component with `next/script`. `afterInteractive` loads the SDK after hydration, and `onLoad` runs `init` once the loader is on the page.

```tsx title="app/components/genuin-embed.tsx"
"use client";
import Script from "next/script";

export function GenuinEmbed({ embedId }: { embedId: string }) {
  return (
    <>
      <div id="gen-sdk" className="gen-sdk-class" data-embed-id={embedId}
           data-api-key="<<YOUR_API_KEY>>" style={{ width: "100%", height: 600 }} />
      <Script
        src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"
        strategy="afterInteractive"
        onLoad={() => window.genuin?.init({})}
      />
    </>
  );
}
```

`next/script` loads a given `src` once per session, so `onLoad` doesn't fire again on client-side navigation. On pages reached that way, call `window.genuin?.init({})` from a `useEffect` as well; already-initialized containers are skipped.

### Angular

Inject the script in `ngAfterViewInit`, once the container exists. The component below is standalone and works in Angular 16 and later.

```ts title="genuin-embed.component.ts"
import { AfterViewInit, Component, Input } from "@angular/core";

@Component({
  selector: "genuin-embed",
  standalone: true,
  template: `<div id="gen-sdk" class="gen-sdk-class" [attr.data-embed-id]="embedId"
    data-api-key="<<YOUR_API_KEY>>" style="width: 100%; height: 600px"></div>`,
})
export class GenuinEmbedComponent implements AfterViewInit {
  @Input({ required: true }) embedId!: string;

  ngAfterViewInit(): void {
    const src = "https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js";
    const genuin = () => (window as any).genuin;
    if (document.querySelector(`script[src="${src}"]`)) { genuin()?.init({}); return; }
    const script = document.createElement("script");
    script.src = src;
    script.async = true;
    script.onload = () => genuin()?.init({});
    document.body.appendChild(script);
  }
}
```

## Configuration reference

### `init` options

Pass these to `window.genuin.init`. Where a data attribute exists, setting it on a container overrides the `init` value for that container.

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| api_key | string | Yes | none | Your brand's API key. Data attribute: data-api-key. |
| embed_id | string | Yes, unless a placement is set | none | The embed to render. Data attribute: data-embed-id. |
| placement_id | string | No | none | With style_id, renders a placement instead of an embed. See Add a Genuin placement to your website. Data attribute: data-placement-id. |
| style_id | string | No | none | The placement style. Data attribute: data-style-id. |
| token | string | No | none | Auto-login token for the current visitor. Empty, "null", and "undefined" are ignored. Data attribute: data-token. |
| params | object | No | none | Profile for a new account created from token: name, email, mobile, nickname, profileImage (URL), brandUserIdentity. |
| contextual_params | object | No | none | Page and viewer context: page_context, previous_page_context, user_context, url, geo (lat, long, radius_limit), place, time, user_segments, user_interests, brands_ids, community_ids, loop_ids, posted_by_user_ids. Data attributes: data-page-context, data-previous-page-context, data-user-context, data-url, data-lat, data-long, data-geo, data-place, data-time, data-user-segments, data-user-interests, data-brands-ids, data-community-ids, data-loop-ids, data-posted-by-user-ids; objects and arrays as JSON strings. |
| configuration | object | No | none | Placement only. Overrides the style's style_title, style_subtitle, and sections. |
| start_video_slug | string | No | none | Video to open on load. Sets expand_on_load to true unless you set it to false. Data attribute: data-start-video-slug. |
| action | string | No | none | Interaction to perform on the start video: spark, comment-spark, repost, comment, report, join-community, join-group, subscribe-group, become-a-creator, octo. Data attribute: data-action. |
| comment_id | string | No | none | Comment to focus when the start video opens. Data attribute: data-comment-id. |
| auth_info | object | No | none | signInUrl and signUpUrl, each a string or a function returning one. Sign-in prompts send the visitor there instead of showing the SDK's own flow. |
| error_handler | function | No | none | Called with { isError, isNoContent } when an embed fails to load or its feed is empty. |
| theme | "light" or "dark" | No | "light" | Colour theme of the player. Data attribute: data-theme. |
| useShadowDOM | boolean | No | true | Set false to render into the page's DOM; the SDK's styles then share your cascade. |
| allow_gesture_scroll | boolean | No | true | Set false to turn off swipe and drag between tiles. Data attribute: data-allow-gesture-scroll. |
| video_ids | string | No | none | Comma-separated video ids that replace the feed. Data attribute: data-video-ids. |
| initial_video_ids | string | No | none | Comma-separated video ids to show first, ahead of the feed. Data attribute: data-initial-video-ids. |
| targeting | object | No | none | Ad and sponsorship targeting: keywords (string or array), location, page_url, and an open key_value map of up to 50 entries. Keywords and location are read from the page's <meta name="keywords"> and geo-IP when omitted. |
| sponsorship_id | string[] | No | none | Overrides the sponsorship attached to the embed. An empty array counts as absent. |
| expand_on_load | boolean | No | false | Data attribute only: data-expand-on-load="true" opens the expand view as soon as the embed loads. |
| embed_style | "expand_only" | No | none | Data attribute only: data-embed-style="expand_only" renders no inline player and serves the expand view alone. |
| brand_context, website_type, parent_instance_id, live | — | No | none | Partner and internal options for publisher integrations, preview mode, and nested SDK instances. Don't set them unless Genuin asks you to. |

`contextualParams`, `authInfo`, and `errorHandler` still work as aliases but are deprecated; use the snake-case names.

### `update` options

Pass these to `window.genuin.update` after `init`. Calls made before the SDK is ready are queued.

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| container_id | string | Yes | none | The id of the container to update. |
| token | string | No | none | Signs the visitor into every embed on the page. The TypeScript type lists it as required; an empty string is ignored. |
| user_params | object | No | none | Same keys as params. Listed as required by the type; pass {} when you have nothing to send. |
| contextual_params | object | No | none | Merged into the container's current context, then the feed reloads. |
| targeting | object | No | none | Replaces the container's targeting, so a field you leave out is cleared. |
| start_video_slug | string | No | none | Opens this video in the container's expand view. action and comment_id apply as in init. |

### Methods

| Method | Returns | What it does |
| --- | --- | --- |
| init(config) | Promise | Finds containers and renders them. Safe to call before the bundle has loaded. |
| update(config) | Promise | Changes a container's token, context, targeting, or start video. |
| on(event, listener) | Promise of an unsubscribe function | Subscribes to one of the events below. The listener receives { type, payload, timestamp }. |
| off(event, listener) | Promise | Removes a listener. |
| emit(event, payload) | Promise | Sends a command to the player: player:play, player:pause, player:mute, player:unmute, or sdk:themeChange. |
| expand(containerId) | Promise | Opens the container's expand view. |
| collapse(containerId) | Promise | Closes it. |
| logout() | Promise | Clears the signed-in visitor from every embed. |
| destroy() | void | Unmounts every embed and removes all listeners. Available once the bundle has loaded. |
| version, isLoaded(), isLoading() | string, boolean | The loader's version and bundle state. |

## Events

Subscribe with `on`, which returns a function that unsubscribes. `off(eventType, listener)` removes a listener you kept a reference to. Names outside this list are rejected with a console warning.

```js title="Subscribe"
window.genuin.on("onPlay", (event) => {
  console.log(event.type, event.payload.muted, event.payload.isInView);
});
```

| Event | Payload | Fires when |
| --- | --- | --- |
| onFeedLoaded | videoCount, hasNextPage, isSectioned, feedType, thumbnailUrl, videoUrl | A feed page has loaded |
| onPlay | muted, isInView, isFocused, volume, autoplay | A video starts playing |
| onPause | muted, isInView, isFocused, volume | A video pauses |
| onMuteChange | muted, volume, instanceId | The visitor mutes or unmutes |
| onSwipedForward, onSwipedBackward | fromIndex, toIndex, timestamp | The visitor moves to another video |
| onVideoClicked | videoId | A tile is clicked |
| onExpandViewChanged | true or false | The expand view opens or closes |
| onShare | shareUrl, type, id, clipTitle, clipDescription, clipThumbnailUrl | The visitor shares a video |
| onVideoNotFound | slug, errorCode | A start_video_slug doesn't resolve |
| onAnalyticsTrack | eventName, eventPayload | The SDK records an analytics event, for forwarding to your own analytics |
| onFollowChanged, onCaughtOverlay, checkFollowingStatus, playIHeartContent | varies | Publisher-partner integrations; unused in a standard embed |

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| Nothing renders; the console shows Failed to fetch brand details | The API key is missing or wrong, or belongs to another environment | Copy the key from the Web tab of View Embed Code and pass it as api_key or data-api-key. |
| Console error Placement ID or Style ID is missing, and no embed ID provided | The container has no data-embed-id and init has no embed_id, or only one of placement_id and style_id was set | Add the embed ID, or both placement values. |
| [Genuin SDK Warning]: Found N elements with the same ID | Two containers share an id | Give each container a unique id, such as gen-sdk-1 and gen-sdk-2. |
| No valid SDK elements found for initialization. | init ran before the container existed, or the container lacks the gen-sdk id prefix and class | Render the container first, then call init. |
| window.genuin is undefined | The init script runs before the SDK script tag, or the script failed to load | Place the script tag before the init call and check the URL in the Network tab. |
| The player shows Unable to load content | The embed doesn't exist, the brand's web SDK is off, or a network error occurred | Check the embed ID in Brand Control Center and the failing request in the Network tab; error_handler receives isError: true. |
| The expand view opens but nothing renders inline | The container had zero width or height when init ran | Give the container a width and a height in CSS. |
| The visitor stays signed out after passing token | The auto-login request returned 401, or browser storage is blocked | Check that the token is current and issued for this brand. In a private window or inside a third-party iframe, the SDK keeps the session in memory only, so it ends with the page. |
| Scripts, styles, or requests are blocked | Your Content Security Policy doesn't allow a Genuin host | Add the hosts from the Requirements table. |
| SDK initialization is disabled via URL parameter. | The page URL contains disableGenSdk=true | Remove the parameter. |

## Limitations

- **CDN only**: There's no npm package. Load `https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js` and pin the version.
- **One brand per page**: Sign-in resolves the brand from the first embed, so mixing brands on a page breaks authentication.
- **Containers need a size**: A zero-sized or hidden container is treated as an expand-only host.
- **Shadow DOM**: Your CSS doesn't style the player, and the player uses the Inter font loaded from Google Fonts. There's no font option in 2.0.6.
- **Fixed event list**: `on` accepts only the events listed above. Container resizing is handled internally and isn't subscribable.
- **Context merges, targeting replaces**: `update` merges `contextual_params` into the existing context but replaces `targeting` wholesale.
- **Key-value targeting caps**: 50 entries, keys up to 128 characters, values up to 1,024 characters, keywords up to 100 of 256 characters each, `page_url` up to 2,048 characters. Larger values are dropped with a console warning.
- **Lazy rendering**: Embeds render when they come within 200 px of the viewport, except when a start video or expand-on-load is set.

## See also

- [Add a Genuin placement to your website](https://begenuin.com/developers/developer-resources/sdk/how-do-you-configure-the-genuin-web-sdk-placement-on-your-website)
- [Embed your community on your website](https://begenuin.com/developers/browse-by-products/onsite/can-your-website-become-community-hub-without-building-a-new-platform)
- [How embeds work](https://begenuin.com/developers/browse-by-products/onsite/how-can-you-embed-your-brand-community-on-your-website-using-genuin-embeds)
- [Deliver contextual feeds with page, user, and location context](https://begenuin.com/developers/developer-resources/sdk/contextual-embeds-how-they-work-and-how-to-configure-them)
- [Control what viewers see and can do on embedded videos](https://begenuin.com/developers/browse-by-products/onsite/how-can-brands-manage-overlay-components-and-controls-for-embedded-content)
- [Embed support by platform](https://begenuin.com/developers/browse-by-products/onsite/cross-platform-embeds-supported-specs-integrations-and-limitation)
