Embed Photo Collect in an iFrame
Photo Collect can be embedded with an invitation URL or signed Deeplink. While the capture flow is running inside an iFrame, it sends progress, activity, and content-height messages to the parent page through window.postMessage.
<iframe
id="photo-collect-frame"
src="https://demo.photocollect.io/collect/INVITATION_KEY"
title="Upload your photo"
allow="camera"
></iframe>
The messages are emitted only when Photo Collect is running inside an iFrame.
Message reference
Each message is a plain object in the MessageEvent.data property.
| Type | Payload | Description |
|---|---|---|
photo-collect:process-step |
value: string |
The current capture-flow step. Sent when each Photo Collect page initializes. |
photo-collect:activity |
value: "click" | "focus" | "keyup" |
User activity inside the embedded page. Useful for refreshing an inactivity timer. |
photo-collect:content-resize |
height: number |
The measured content height in CSS pixels. Sent after the page loads and when its window is resized, but only when the height changed. |
Common process-step values are index, to, signature, photo, review, verification, payment, rating, and finalize. The enabled invitation options determine which steps occur. Error or unavailable states can also report values such as expired, exhausted, maintenance, or notfound.
The messages themselves do not contain a timestamp. If you need one for logging or inactivity tracking, add it in the parent when the message is received.
Listen for messages
Validate both the sender's origin and its window before using a message. The example below tracks the current step, records activity, and keeps the iFrame height synchronized with its content.
<script>
const frame = document.querySelector('#photo-collect-frame');
const photoCollectOrigin = new URL(frame.src).origin;
window.addEventListener('message', (event) => {
if (
event.origin !== photoCollectOrigin ||
event.source !== frame.contentWindow
) {
return;
}
const message = event.data;
if (!message || typeof message !== 'object') {
return;
}
const timestamp = new Date().toISOString();
switch (message.type) {
case 'photo-collect:process-step':
if (typeof message.value === 'string') {
console.log({
type: message.type,
value: message.value,
timestamp
});
}
break;
case 'photo-collect:activity':
if (['click', 'focus', 'keyup'].includes(message.value)) {
console.log({
type: message.type,
value: message.value,
timestamp
});
}
break;
case 'photo-collect:content-resize':
if (Number.isFinite(message.height) && message.height > 0) {
frame.style.height = `${Math.ceil(message.height)}px`;
console.log({
type: message.type,
height: message.height,
timestamp
});
}
break;
}
});
</script>
For example, moving from the introduction to photo capture can produce messages like these in the parent-side log:
type: photo-collect:process-step | value: index | timestamp: 2026-08-07 01:00:08
type: photo-collect:content-resize | height: 755 | timestamp: 2026-08-07 01:00:08
type: photo-collect:activity | value: focus | timestamp: 2026-08-07 01:00:14
type: photo-collect:activity | value: click | timestamp: 2026-08-07 01:00:14
type: photo-collect:process-step | value: photo | timestamp: 2026-08-07 01:02:43
type: photo-collect:content-resize | height: 597 | timestamp: 2026-08-07 01:02:43
Integration notes
- Treat activity events as signals, not as proof that a process step was completed. A single interaction can produce both
focusandclickevents. - Use
photo-collect:process-stepto update surrounding UI or analytics. Use your configured redirect or the API workflow to determine that the overall business process is complete. - Set the iFrame height from
photo-collect:content-resizeinstead of reading its document. Browsers prevent direct DOM access when the parent and Photo Collect use different origins. - Keep the expected Photo Collect origin in application configuration when the iFrame URL is generated dynamically. Never accept Photo Collect message types from every origin.
- The iFrame needs camera permission when users capture a photo directly. If your site sends a
Permissions-Policyheader, make sure it does not block camera access for the Photo Collect origin. collect_redirect_uriwill redirect inside the child frame, not the parent frame.