Product hydration
Product hydration allows your app to supply enriched product data (pricing, images, variants, stock) to the Bambuser live player at runtime. When a live show starts, the player fires a provide-product-data event listing all products it needs data for. Your app looks up each product by SKU and pushes the data back into the player using invoke("updateProductWithData", ...).
Prerequisites
currency and locale must be set in your player configuration — product hydration will not work without them.
let playerView = bambuserPlayer.createPlayerView(
videoConfiguration: .init(
type: .live(id: "your-show-id"),
events: ["*"],
configuration: [
"currency": "USD", // Required for product hydration
"locale": "en-US" // Required for product hydration
]
)
)
playerView.delegate = self
Handle the provide-product-data Event
The player fires provide-product-data at show start, and again whenever a new product needs to be displayed. Handle it in your BambuserVideoPlayerDelegate:
func onNewEventReceived(id: String, event: BambuserEventPayload) {
if event.type == "provide-product-data" {
Task {
try await self.hydrate(data: event.data)
}
}
}
The event payload contains an "event" dictionary with a "products" array. Each entry has:
| Field | Type | Description |
|---|---|---|
id | String | Bambuser-generated product ID — pass this back to the player |
ref | String | Your product SKU — use this to look up your own data |
url | String | Product URL as set in the Bambuser dashboard |
Send Product Data Back to the Player
Call invoke("updateProductWithData", ...) for each product. The arguments string must start with the Bambuser product id in single quotes, followed by a comma and the product object:
func hydrate(data: [String: Any]) async throws {
guard let event = data["event"] as? [String: Any],
let products = event["products"] as? [[String: Any]] else { return }
for product in products {
guard let id = product["id"] as? String,
let sku = product["ref"] as? String else { continue }
// Look up product data from your own catalog using the SKU
guard let productData = YourProductService.product(for: sku) else { continue }
try await self.playerView?.invoke(
function: "updateProductWithData",
arguments: """
'\(id)', {
sku: '\(productData.sku)',
name: '\(productData.name)',
brandName: '\(productData.brand)',
introduction: '\(productData.shortDescription)',
description: '\(productData.description)',
variations: [
{
sku: '\(productData.variationSku)',
name: '\(productData.variationName)',
colorName: '\(productData.colorName)',
imageUrls: ['\(productData.imageUrl)'],
sizes: [
{
sku: '\(productData.sizeSku)',
currency: 'USD',
current: \(productData.price),
original: \(productData.originalPrice),
name: '\(productData.sizeName)',
inStock: \(productData.stock),
}
]
}
]
}
"""
)
}
}
Full Product Data Structure
The product object passed to updateProductWithData supports the following fields:
Product
| Field | Type | Required | Description |
|---|---|---|---|
sku | String | Yes | Your product SKU / identifier |
name | String | Yes | Product display name |
brandName | String | Yes | Brand name |
introduction | String | No | Short introductory text |
description | String | No | Full description — supports HTML |
variations | Array | Yes | List of product variations (colors/styles) |
Variation
| Field | Type | Required | Description |
|---|---|---|---|
sku | String | Yes | Variation SKU |
name | String | Yes | Variation display name |
colorName | String | Yes | Color name shown in the variation selector |
colorHexCode | String | No | Hex color code e.g. "#000000" |
imageUrls | [String] | Yes | Ordered list of image URLs for this variation |
sizes | Array | Yes | List of sizes/SKUs for this variation |
Size
| Field | Type | Required | Description |
|---|---|---|---|
sku | String | Yes | Size-level SKU |
name | String | Yes | Size name e.g. "Small", "XL" |
current | Double | Yes | Current (sale) price |
original | Double | No | Original price — shown as strike-through |
currency | String | Yes | Three-letter currency code e.g. "USD" |
inStock | Int | Yes | Available stock quantity (0 = out of stock) |
perUnit | Double | No | Price per unit (for bundle/multi-pack pricing) |
unitAmount | Int | No | Quantity per unit |
unitDisplayName | String | No | Unit label e.g. "kg", "L", "st" |
Using the Builder Pattern (Recommended)
Instead of raw string interpolation, use the HydratedProduct builder from the SDK example app. The builder validates required fields and serialises the product to the correct format:
func hydrateUsingBuilder(data: [String: Any]) async throws {
guard let event = data["event"] as? [String: Any],
let products = event["products"] as? [[String: Any]] else { return }
for product in products {
guard let sku = product["ref"] as? String,
let id = product["id"] as? String else { continue }
guard let source = YourProductService.product(for: sku) else { continue }
let hydratedProduct = try HydratedProduct(sku: source.sku)
.withName(source.name)
.withBrandName(source.brand)
.withVariations(
source.variations.map { v in
try Variation()
.withSku(v.sku)
.withName(v.name)
.withColorName(v.colorName)
.withImageUrls(v.imageUrls)
.withSizes(
v.sizes.map { s in
try ProductSize()
.withSku(s.sku)
.withName(s.name)
.withCurrentPrice(s.currentPrice)
.withInStock(s.inStock)
.build()
}
)
.build()
}
)
.build()
let hydrationString = "'\(id)', \(try hydratedProduct.toJSON())"
try await playerView?.invoke(
function: "updateProductWithData",
arguments: hydrationString
)
}
}
Notes
- The player may fire
provide-product-datamultiple times during a show as new products are highlighted. Your hydration handler must be idempotent. - Fields not provided in your hydration response fall back to data previously scraped or manually set in the Bambuser dashboard.
invokeis anasync throwsmethod — always call it inside aTaskorasynccontext.