Published on January 19, 2023 (over 1 year ago)

Build live shopping experiences with Mux and Impekable

Asiya Gorelik
By Asiya Gorelik8 min readPartnerships & Product

What will the future of e-commerce look like? The see-now-want-now consumer mentality has created a clear path to live video shopping — and you don’t want to be left behind.

So, where does Mux fit in? You may remember  when we launched Real-Time Video earlier this year. There are lots of ways Real-Time Video can take your app to the next level, and one of them happens to be building a live shopping experience.

For example, take our friends at Impekable — a 10-year-old, award-winning digital product studio born in the San Francisco Bay area. They’ve done some pretty cool things with live shopping experiences, including building and redesigning the website and mobile experience (which included live shopping!) for Thailand’s largest retailer, Central Department Store.

Needless to say, Impekable knows a thing or two about creating an interactive live shopping experience, so we were really excited to see what they built for live shopping with our Real-Time Video product and the Stripe payment processing platform. And now we’re even more excited to share it with you.

Here’s Pek, founder and CEO of Impekable, to discuss why live shopping matters and walk you through how his team built an interactive live shopping experience with Real-Time Video and Stripe.

LinkWhy should you care about live shopping?

I don’t think anybody doubts that online e-commerce is important in today’s age. Amazon and Shopify have made it clear you need an online presence or you will get left behind. If you want to get ahead of the game, it’s time to incorporate live shopping into your sales motion. But don’t just take my word for it:

  1. Conversion rates are up to 10 times higher1 for live commerce than conventional e-commerce.
  2. By 2024, e-commerce revenue in the US from live online shopping will increase threefold, reaching $35 billion2.
  3. If you are selling luxury goods, livestream selling has an average 70% conversion rate.3
  4. 60% of shoppers4 who tried livestream shopping reported that it improved their shopping experience.
  5. Livestream shopping purchases have increased 76% 5during the pandemic.
  6. China’s livestream shopping market grew from $3 billion to $171 billion in 3 years.6

Why is live shopping superior to plain e-commerce? In addition to all the benefits of online e-commerce — including buy anywhere, anytime — livestream shopping offers qualities associated with physical shopping, like human interaction.

LinkWhat we built, and why

In the same way Shopify made it easy for anyone to have an e-commerce store, we wanted to show that a simple live shopping experience is accessible and easy to build with Mux’s Real-Time Video and Stripe. Inspired by Instagram’s live shopping feature, we translated the mobile experience into a web desktop-based demo. Here’s a small video of what we came up with.

In this demo, the Presenter can stream their live video and toggle a product content overlay on top of the Viewer video player with a title, description, and shoppable link available via Stripe. Think of it as a QVC-like live stream: multiple presenters are able to connect and talk about the current product in a Zoom or Google Meet-like format, while viewers are able to purchase when the product link pops up. Viewers are also able to engage with the presenter through a live chat.

LinkBuilding the live shopping experience: a step-by-step guide

For this demo, we adapted the Mux Meet code repository, which is a video chat room similar to Zoom or Google Meet. From there, we created a presenter view that allows a user to create a viewable live stream by using Mux Spaces to broadcast camera-enabled web browsers. Viewers navigate to a link containing the Mux Space created by the presenter to tune in to the live stream and participate in a group chat.

From here, the presenter will stream their live video. They can toggle a product content overlay on top of the viewer video player with a title, description, and shoppable link available via Stripe. Think of it as a QVC-like live stream, with multiple presenters able to connect and talk about the current product while viewers are able to purchase when the product link pops up.

This demo is bootstrapped by Next.js with create-next-app.

Alright. Let's move on to the technical implementation.

npm install @mux/spaces-web @mux/mux-node @mux/mux-player-react -save

Clicking “Create Channel” will create a Mux Space and a Live Stream object. The user will be redirected to the presenter control screen, where they’ll be able to view their product and livestream toggles as well as a chat window or real-time conversation with prospective customers.

Creating a new Mux Space from this UI makes a request to our NextJS-provided API function with the provided topic and presenter name.

const createSpace = () => fetch("/api/spaces", { headers: { "Content-Type": "application/json", }, method: "POST", body: JSON.stringify({ topic: topicName, host: participantName, }), }) .then((res) => res.json()) .then((data: Space) => { router.push(`/p/${}`); }) );

With this, we'll have an endpoint to create a new Space.

import { NextApiRequest, NextApiResponse } from "next"; import { StatusCodes } from "http-status-codes"; import Mux from "@mux/mux-node"; const { Video } = new Mux(); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method === "POST") { const { topic } = req.body; const liveStream = await Video.LiveStreams.create({ playback_policy: "public", embedded_subtitles: [], passthrough: host, }); const space = await Video.Spaces.create({ type: "server", passthrough: topic, broadcasts: [ { live_stream_id: || "", }, ], }); res.status(StatusCodes.CREATED).json(space); } else { res.status(StatusCodes.METHOD_NOT_ALLOWED).end(); } }

NextJS api endpoints export a default `handler` function that acts as the http route for incoming requests. In this example, we are checking if the request is a `POST` method; if not, we end the request.

Since we don't have existing streams or broadcasts, we're creating a new one every time a Space is made. This allows us to pass the live stream ID so the Space knows where to send its broadcasts when going live. We're also expecting `topic` from the incoming request so we can attach it to the Space via the `passthrough` key, which is an arbitrary title for the Space.

Here, the presenter can enable their video and audio and take note of the product control on the bottom left, as well as the “Start Live Stream” button. This button enables the previously created Space and Live Stream to start broadcasting, which essentially signals the video coming from the Space to send video data to the Live Stream via a Playback ID reference.

The viewer experience looks similar to the presenter view, but without the controls:

Whenever the presenter decides to start live streaming from their browser, we implement WebSockets, using, to communicate events from our API server to all of the viewers, informing them when one of the following happens:

  • A live stream is beginning or ending
  • A product should be visible
  • A new comment has been posted

We initialize the socket on the server and register all of the events that can be sent to viewers.

const onConnection = (socket: Socket) => { const { roomId } = socket.handshake.query; socket.join(roomId as string); socket.on(INCOMING_CHAT_MESSAGE_EVENT, (data) => { as string).emit(INCOMING_CHAT_MESSAGE_EVENT, data); }); const toggleProductDisplay = (displayProduct: boolean) => { socket.broadcast.emit(DISPLAY_PRODUCT, displayProduct); }; socket.on(TOGGLE_PRODUCT, toggleProductDisplay); socket.on("disconnect", () => { socket.leave(roomId as string); }); };

Those server-sent events have a corresponding receiver on the client side to inform the app what should be done with the information:

type SpaceData = { status: "active" | "idle"; space_id: string; live_stream_id: string; layout: "gallery"; id: string; }; export type WebhookObject = { id: string; type: string; data: SpaceData; }; export const enum MuxEventType { SpaceBroadcastActive = "", SpaceBroadcastIdle = "", LiveStreamActive = "", LiveStreamIdle = "video.live_stream.idle", } export interface MuxWebhookConfig<P = any> { action: (params: P, socket: Server) => Promise<void>; } export const MuxWebhookAction: Record<MuxEventType, MuxWebhookConfig> = { [MuxEventType.SpaceBroadcastActive]: { async action(_params: SpaceData, io: Server) { io.emit(BROADCAST_START); return Promise.resolve(); }, }, [MuxEventType.SpaceBroadcastIdle]: { async action(_params: SpaceData, io: Server) { io.emit(BROADCAST_STOP); return Promise.resolve(); }, }, [MuxEventType.LiveStreamActive]: { async action(params: SpaceData, io: Server) { io.emit(LIVESTREAM_START); return Promise.resolve(); }, }, [MuxEventType.LiveStreamIdle]: { async action(params: SpaceData, io: Server) { io.emit(LIVESTREAM_STOP); return Promise.resolve(); }, }, };

The events that begin with “…” are built-in Mux events using the Spaces SDK. These helpers allow us to change UI states between the broadcast beginning and the live stream ending, so that we can give granular updates to the viewers, such as a loading spinner when the live streamer has begun the broadcast but the live stream has not yet reached the playback state.

When the presenter decides it’s time to start their live stream, the Space notifies all of the viewers that the broadcast is beginning:

const broadcast = space.broadcasts?.at(0); if (!broadcast) { return res.status(StatusCodes.NOT_FOUND).json({}); } await Video.Spaces.Broadcasts.start(,;

Finally, the viewer is informed the live stream will begin shortly. For simple playback, we use Mux Player with the live stream’s Playback ID.

<MuxPlayer autoPlay={autoPlay || isLive} streamType="live" playbackId={data.playback?.id} muted={isMuted} volume={playerVolume} style={{ width: "100%", height: "100%", objectFit: "cover", maxHeight: "580px", }} />

Other socket events, such as when the presenter clicks the “Show Product” button, are displayed to both the presenter and the viewer:

Notice how the presenter controls are absent from the viewer’s side:

The product block that the presenter is pointing at contains an actual Stripe Checkout link connected to the presenter’s store. From there, the viewer can click the “Buy Now” link to go directly to the purchase page:

Finally, when the presenter decides to end the broadcast, the “Stop Live Stream” button is clicked, and the event is sent across all viewers’ browsers:

const initalizeSocket = useCallback(async () => { socketRef.current = socketIOClient(); . socketRef.current.on(BROADCAST_STOP, () => { refetch(); setStatus("ended"); }); }, [refetch]);

LinkReady to create your own live shopping experience?

Now that you’ve seen how easy it is to get up and running with live shopping, here are some resources to help you start creating your own experience:

  1. Mux Spaces SDK and Mux Node SDK for easy integration with Mux objects and APIs
  2. Mux Player for a cross-browser video experience
  3. Stripe Checkout for the shopping cart checkout experience
  4. WebSockets for chat
  5. NextJS for the UI framework and API functionality

If you need a hand with building your own live shopping experience, Mux partner Impekable can help you. Contact


Written By

Asiya Gorelik

Loves all things product marketing. Was often described as "a pleasure to have in class". Can be found outside with her pups or hunting down the best pizza in town.

Leave your wallet where it is

No credit card required to get started.