RoveIQ Embed

Put an interactive RoveIQ wayfinding map inside your own web page, then drive it from your page’s code — change floors, highlight a store, draw walking directions — and react as visitors explore it. It’s an iframe plus a postMessage API: no SDK, no API key.

Quick start

A working integration is four steps. Everything after this section is detail on top of it.

1. Drop in the iframe

Point it at your venue. Your RoveIQ contact will give you the venue ID.

<iframe
	id="roveiq"
	src="https://embed.roveiq.com/venues/<venueId>"
	title="Venue map"
	allow="geolocation"
	style="width: 100%; height: 600px; border: 0;"
></iframe>

2. Listen for messages

The map talks to your page with window.postMessage. Filter on source so you only see RoveIQ traffic.

window.addEventListener('message', (event) => {
	// Ignore anything that isn't from the map.
	if (event.data?.source !== 'roveiq-embed') return;

	const { type, payload } = event.data;
	console.log(type, payload);
});

3. Wait for embed:ready

The map sends this once it has finished loading and can accept commands. It carries the venue’s floor list.

let floors = [];

window.addEventListener('message', (event) => {
	if (event.data?.source !== 'roveiq-embed') return;

	if (event.data.type === 'embed:ready') {
		floors = event.data.payload.floors;
		// Safe to send commands from here on.
	}
});

4. Send commands

Post back to the iframe with the matching host envelope. This helper is all you need.

const iframe = document.getElementById('roveiq');

function send(type, payload) {
	iframe.contentWindow.postMessage({ source: 'roveiq-embed-host', type, payload }, '*');
}

send('map:highlightLocation', { locationId: 1234 });
send('map:showFloor', { floorId: floors[0].id });

Embedding the map

The URL

Every embed points at a single venue, identified by its numeric ID:

https://embed.roveiq.com/venues/<venueId>

You can also deep-link straight to a location, so the map opens with that store already selected and its details panel showing:

https://embed.roveiq.com/venues/<venueId>/locations/<locationId>

iframe attributes

AttributeWhy
allow="geolocation"Required for blue-dot positioning and walking directions from the visitor’s current position. Omit it and those features stay off. The browser still asks the visitor for permission.
titleDescribes the frame for screen readers. Something like “Venue map” is fine.
idAny value — you need a handle on the element to send it commands.

Sizing

The map fills whatever box you give it, so the iframe needs a real height — it will collapse if it only has a percentage height inside an auto-height parent. Two approaches that work:

/* Fixed height, full width. Simplest. */
#roveiq {
	width: 100%;
	height: 600px;
	border: 0;
}

/* Responsive: hold an aspect ratio on desktop,
   go taller on narrow screens so the map stays usable. */
#roveiq {
	width: 100%;
	aspect-ratio: 16 / 10;
	border: 0;
}

@media (max-width: 700px) {
	#roveiq {
		aspect-ratio: auto;
		height: 70vh;
	}
}

Sizing tip — The map has a mobile layout that engages below roughly 768 px of frame width, not window width. A narrow iframe on a wide desktop page will show the mobile interface — usually what you want, but worth knowing if a narrow sidebar embed looks unexpected.

Content Security Policy

If your site sends a CSP header, allow the embed origin as a frame source:

Content-Security-Policy: frame-src https://embed.roveiq.com;

URL options

Append these as query parameters to tailor what the embed shows. All are optional, and all take 1 to switch on unless noted.

https://embed.roveiq.com/venues/<venueId>?hidesearch=1&zoom=19

Hiding interface

ParameterEffect
hidesearch=1Hides the search bar.
hideamenities=1Hides the amenities section (restrooms, ATMs, and similar).
hidecontrols=1Hides all map controls and menus — zoom buttons, the floor changer, the side panel. Use this when your page provides its own interface and drives the map through the API.
desktopDirections=0Turns off the directions interface on desktop, where it is on by default.

Opening view

ParameterRangeEffect
zoom0–22Starting zoom. 0 is fully out, 22 fully in.
pitch0–85Camera tilt in degrees. 0 is straight down.
bearing0–359Rotation in degrees. 0 is north, 90 east, 180 south, 270 west.

Location services

ParameterEffect
gpsEnabled=1Turns on positioning so visitors can see where they are and get directions from there. Needs allow="geolocation" on the iframe.
enableDesktopGps=1Extends positioning to desktop, where it is off by default.

Kiosks and touch screens

ParameterEffect
disableInteraction=1Makes the map display-only. Panning, zooming and tapping are all switched off.
emitclicks=1Posts a plain { type: 'resetInactivityTimerDataType' } message on every tap and keypress, so a kiosk shell can keep its screensaver from firing while someone is using the map. This one message does not use the envelope described below.

Message envelope

Everything in both directions uses the same three-field shape, so you can tell RoveIQ traffic apart from any other postMessage activity on your page — analytics tags, chat widgets, other iframes.

// Map → your page
{ source: 'roveiq-embed',      type: 'map:floorChange', payload: {} }

// Your page → map
{ source: 'roveiq-embed-host', type: 'map:showFloor',   payload: {} }

Always check source before reading a message. Anything without the matching value is ignored in both directions.

Send from the hosting page — Commands are only accepted from the window that embedded the iframe — that is, iframe.contentWindow.postMessage(...) called from your own page. A message from anywhere else, such as another frame on the page or a popup, is rejected even with the correct source value. That string is a label, not a credential.

Startup and readiness

The map takes a moment to load its floor data and draw. Commands sent before it is ready fail, so wait for embed:ready.

WhoWhat happens
Your pageAdds the iframe and starts listening.
MapLoads venue data and renders. Silent throughout.
Map →Sends embed:ready with the venue name, floor list and current floor.
Your page →Sends commands. Receives map events as the visitor explores.

If you start listening late

If your listener might attach after the map has already loaded — a component that mounts later, a tab that opens on demand — send embed:ping and the map replies with a fresh embed:ready.

send('embed:ping');

A ping that arrives while the map is still loading gets no reply. That is deliberate — you would rather have silence than an embed:ready that isn’t true yet. There is no need to retry or poll: the map sends embed:ready on its own the moment it finishes.

Events from the map

These arrive on your message listener as the visitor uses the map. You do not subscribe to anything — every embed sends all of them.

embed:ready

Map → your page. The map has finished loading and accepts commands. Sent once on load, and again whenever you send embed:ping.

FieldTypeNotes
venueIdnumberThe venue being shown.
venueNamestringDisplay name of the venue.
floors{ id, name, default }[]Every floor, in display order. Use these IDs with map:showFloor.
currentFloorIdnumberThe floor showing right now.
commandsstring[]Every command this version of the embed accepts. Useful for feature-detection.

map:pointerclick

Map → your page. Sent for every click or tap on the map, whether or not it landed on something. Read targetType first — it tells you which of the other fields are filled in.

FieldTypeNotes
targetTypestringlocation, pin, shape3d, or background when nothing was hit.
locationIdnumberFor location and shape3d.
location{ id, name }When the clicked shape has a store attached.
pinIdstringFor pin.
labelTextstringThe label drawn on the shape, if any.
floorIdnumberFloor showing when the click happened.
coordinates[lng, lat]Where the click landed.
point{ x, y }Screen position in CSS pixels, for positioning your own tooltip.
if (type === 'map:pointerclick' && payload.targetType === 'location') {
	openStoreDetails(payload.locationId, payload.location.name);
}

map:hover

Map → your page. Sent when the shape under the pointer changes, including when the pointer moves onto empty space. Desktop only, and frequent — throttle any expensive work you hang off it.

FieldTypeNotes
hoveringbooleanfalse means the pointer left; no other fields are present.
locationIdnumberWhen the hovered shape has a store attached.
location{ id, name }Same.
labelTextstringThe shape’s label. May be empty.

map:floorChange

Map → your page. The displayed floor changed — however it happened. This covers your own map:showFloor command, the visitor using the floor changer, and a route stepping between floors. If your page shows its own floor selector, follow this to keep the two in step.

FieldTypeNotes
idnumberUse with map:showFloor.
namestringThe floor’s name.
indexnumberDisplay order. Lowest floor is 0.

map:routeResult

Map → your page. The outcome of a map:route command. Exactly one of these arrives for every route you request, whether it worked or not — this is the message to use for “did my route command succeed”.

FieldTypeNotes
okbooleanWhether a route was found and drawn.
tostringThe destination you asked for, as type\|id.
fromstringAbsent when the route started from the visitor’s position.
legsnumberHow many floor-to-floor segments. Only when ok.
totalDistancenumberWalking distance in metres. Only when ok.
if (type === 'map:routeResult') {
	if (payload.ok) {
		showDistance(Math.round(payload.totalDistance) + ' m walk');
	} else {
		showMessage("We couldn't find a walking route there.");
	}
}

map:routeFound

Map → your page. The shape of a route that was drawn, one entry per floor it crosses. Use this if you want the geometry — to draw your own overview, or to measure a leg.

FieldTypeNotes
legsRouteLeg[]One per floor segment.
legs[].floorIdnumberFloor this segment is on.
legs[].floorNamestringThat floor’s name.
legs[].path[lng, lat][]The walking line for this segment.

Not sent for every route — This one is best-effort: it is skipped for routes that specify a from, and while live positioning is active. For a signal you can rely on for every request, use map:routeResult above.

map:noRoute

Map → your page. No walking route could be found to the requested destination. Sent alongside a map:routeResult with ok: false.

FieldTypeNotes
idnumber \| stringThe destination that could not be reached.

map:arrivedFloorChange

Map → your page. During live navigation, the visitor has reached the point where the route changes floors — an escalator, a lift, a staircase. Same fields as map:floorChange, describing the floor they are heading to. Requires positioning to be switched on.

map:error

Map → your page. The map hit a problem. Check fatal first: when it is true the map has stopped drawing and needs to be reloaded.

FieldTypeNotes
fatalbooleanThe map is unusable and should be reloaded.
codestringMachine-readable cause. See below.
messagestringHuman-readable description, for your logs.
contextobjectExtra detail about what was happening.

Current codes are webgl-context-lost, invalid-floor, asset-load-failed, render-failed and unknown. Treat this as an open list — handle unfamiliar values rather than matching on all of them exhaustively.

embed:commandError

Map → your page. A command you sent could not be carried out — an unknown floor ID, a missing locationId, or a command that arrived before the map was ready.

FieldTypeNotes
typestringThe command that failed.
messagestringWhat went wrong.

Commands to the map

Send these with the send() helper from the quick start. Anything that fails comes back as embed:commandError.

map:showFloor

Your page → map. Switches the displayed floor. Use an ID from the floors list in embed:ready.

send('map:showFloor', { floorId: 12 });

map:flyToLocation

Your page → map. Centres and zooms the map on a store, changing floors if it is on a different one. Does not highlight it — pair with map:highlightLocation if you want both.

send('map:flyToLocation', { locationId: 1234 });

map:highlightLocation

Your page → map. Draws a store in the venue’s highlight colour. Highlights persist until cleared, and you can have several at once.

send('map:highlightLocation', { locationId: 1234 });

map:clearHighlight

Your page → map. Removes one highlight, or all of them.

send('map:clearHighlight', { locationId: 1234 }); // just this one
send('map:clearHighlight', {}); // all of them

map:route

Your page → map. Draws walking directions. Omit from to start at the visitor’s current position.

// Between two stores
send('map:route', {
	from: { type: 'location', id: 1234 },
	to: { type: 'location', id: 5678 }
});

// From wherever the visitor is standing
send('map:route', { to: { type: 'location', id: 5678 } });

// Step-free route to the nearest restroom
send('map:route', {
	from: { type: 'location', id: 1234 },
	to: { type: 'amenity', id: 12 },
	handicap: true
});
FieldTypeNotes
toRouteEndpointRequired. See Route endpoints.
fromRouteEndpointOmit to start from the visitor’s position.
handicapbooleanStep-free routing. Defaults to false.
zoomToFitbooleanFrame the whole route. Defaults to true.

Routing from the visitor’s position — When you omit from, to must be a location. Amenity and coordinate destinations need an explicit from — sent without one they are rejected with an embed:commandError rather than quietly routing somewhere wrong.

Routes drawn this way drive the map directly. They do not fill in the embed’s own directions panel or its turn-by-turn view.

map:clearRoute

Your page → map. Ends routing, erases the drawn route, and clears any highlights the route added. No payload.

send('map:clearRoute');

embed:ping

Your page → map. Asks the map to re-send embed:ready. No payload. See Startup and readiness.

Handling errors

Two messages report trouble, and they mean different things.

MessageMeansWhat to do
embed:commandErrorYour command was rejected. The map is fine.Check the message. Usually a bad ID or a command sent before embed:ready.
map:errorThe map itself hit a problem.Log it. If fatal is true, reload the iframe.
window.addEventListener('message', (event) => {
	if (event.data?.source !== 'roveiq-embed') return;
	const { type, payload } = event.data;

	if (type === 'embed:commandError') {
		console.warn('RoveIQ rejected', payload.type, payload.message);
	}

	if (type === 'map:error' && payload.fatal) {
		// Reloading the frame rebuilds the map from scratch.
		iframe.src = iframe.src;
	}
});

Route endpoints

The from and to fields of map:route each take a route endpoint. Write it as an object, or as the equivalent type\|id string — they are interchangeable.

TypeObject formString form
location{ type: 'location', id: 1234 }'location\|1234'
amenity{ type: 'amenity', id: 12 }'amenity\|12'
coordinate{ type: 'coordinate', id: '-84.51,39.10,12' }'coordinate\|-84.51,39.10,12'

A coordinate is longitude,latitude,floorId — the floor matters, since the same point on the ground exists on every level. Any other type is rejected with an embed:commandError.

Common recipes

Your own store directory

Hide the built-in interface, list stores in your own markup, and drive the map from your clicks.

// Load with ?hidecontrols=1&hidesearch=1

document.querySelectorAll('.store').forEach((button) => {
	button.addEventListener('click', () => {
		const id = Number(button.dataset.locationId);
		send('map:clearHighlight', {});
		send('map:flyToLocation', { locationId: id });
		send('map:highlightLocation', { locationId: id });
	});
});

Keep your floor selector in step

Follow map:floorChange so your control tracks the map even when the visitor changes floors some other way.

const select = document.getElementById('floor-select');

select.addEventListener('change', () => {
	send('map:showFloor', { floorId: Number(select.value) });
});

window.addEventListener('message', (event) => {
	if (event.data?.source !== 'roveiq-embed') return;
	const { type, payload } = event.data;

	if (type === 'embed:ready') {
		select.innerHTML = payload.floors
			.map((f) => `<option value="${f.id}">${f.name}</option>`)
			.join('');
		select.value = payload.currentFloorId;
	}

	// Covers the floor changer, and routes that cross floors.
	if (type === 'map:floorChange') {
		select.value = payload.id;
	}
});

“Take me there” button

function walkTo(locationId) {
	send('map:route', { to: { type: 'location', id: locationId } });
}

// Report the outcome back to the visitor.
if (type === 'map:routeResult') {
	status.textContent = payload.ok
		? Math.round(payload.totalDistance) + ' m walk'
		: 'No walking route available.';
}

Limits and caveats

Worth knowing before you build

  • Wait for embed:ready. Commands sent earlier are rejected. There is no queue.
  • IDs come from RoveIQ. Venue, location and amenity IDs are issued by the RoveIQ portal. Your contact can supply the list for your venue, or you can read floor IDs out of embed:ready.
  • Positioning needs permission. Anything involving the visitor’s location — routing without a from, map:arrivedFloorChange — needs allow="geolocation" on the iframe, the gpsEnabled=1 parameter, and the visitor accepting the browser prompt.
  • API-drawn routes stay on the map. They do not populate the embed’s own directions panel or turn-by-turn list.
  • map:hover is desktop-only and fires often. Throttle anything expensive you attach to it.

Not currently reported

The map does not yet send events for zoom and pan, search activity, amenity toggles, or turn-by-turn navigation progress. If your integration needs any of these, tell your RoveIQ contact — they are straightforward to add.

Questions, venue IDs, or a capability you need that isn't here — talk to your RoveIQ contact.