Nhúng qua iframe (SSO)
Phương án nhúng đơn giản nhất cho CRM legacy không sửa được source frontend hoặc khi đối tác chỉ muốn "treo" webphone Zorio mà không cần controlling nó. Iframe đã được build sẵn UI đầy đủ (dialer, popup incoming, in-call panel), chỉ cần <iframe src=...> là chạy.
1. Cú pháp tối giản
<iframe
src="https://app.zorio.vn/webphone-embed?token=<jwt>"
allow="microphone"
style="border:0; width:380px; height:560px"
></iframe>Tên miền tuỳ chỉnh
Thay app.zorio.vn bằng tên miền tuỳ chỉnh riêng nếu đối tác đã được cấp custom domain (vd pbx.crm-cua-ban.vn). Iframe luôn host trên đúng tên miền cấp cho khách hàng để bearer token / SIP credentials không bị cross-account.
2. SSO exchange — lấy <jwt>
JWT truyền qua query string là short-lived embed token (TTL mặc định 5 phút), được backend của đối tác đổi từ session user của họ qua API:
POST https://acme.zorio.example/api/webphone/embed-token
Authorization: Bearer <bearer-token-từ-/api/auth/login>
Content-Type: application/json
{
"ttl_seconds": 300
}Response:
{
"embed_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-06-30T10:35:00+07:00"
}Không expose bearer token chính
Tuyệt đối không truyền bearer token gốc vào src của iframe. Bearer dài hạn lộ qua URL sẽ ghi vào Referer header / log proxy. Luôn đổi sang embed token TTL ngắn rồi inject vào iframe.
Flow điển hình
[CRM của đối tác] [Backend CRM] [Zorio API]
│ │ │
│ cần render webphone │ │
├─────────────────────────────►│ │
│ │ POST /webphone/embed-token│
│ ├───────────────────────────►│
│ │ │
│ │◄───────────────────────────┤
│ │ embed_token (TTL 5 phút) │
│◄─────────────────────────────┤ │
│ embed_token │ │
│ │ │
│ <iframe src="?token=..."> │
│ → mount + SIP register │ │Khi token gần hết hạn, frame parent có thể fetch token mới rồi gửi qua postMessage (xem postMessage API).
3. Cấu hình query string đầy đủ
<iframe
src="https://app.zorio.vn/webphone-embed
?token=<jwt>
&layout=docked
&theme=auto
&locale=vi
&auto_register=1"
allow="microphone"
style="border:0; width:380px; height:560px"
></iframe>| Tham số | Giá trị | Mô tả |
|---|---|---|
token | JWT embed | Bắt buộc — short-lived embed token |
layout | docked | overlay | fullscreen | Bố cục UI. Default: docked |
theme | light | dark | auto | Default: auto |
locale | vi | en | Default: vi |
auto_register | 1 | 0 | Tự register SIP sau load. Default: 1 |
4. Quyền microphone trên iframe
Bắt buộc thêm allow="microphone" — nếu thiếu, browser sẽ block getUserMedia ngay cả khi user đã cấp quyền cho parent page:
<iframe allow="microphone" ... ></iframe>
<!-- Nếu cần thêm camera (video Phase 2): -->
<iframe allow="microphone; camera" ... ></iframe>Cross-origin
Iframe app.zorio.vn luôn cross-origin với CRM của đối tác → mọi giao tiếp giữa parent và iframe phải qua postMessage (xem postMessage API). Không cố truy cập iframe.contentWindow.phone trực tiếp — sẽ bị browser block.
5. postMessage API
Iframe webphone giao tiếp với parent qua window.postMessage. Mọi message đều có format:
interface ZorioWebphoneMessage {
source: 'zorio-webphone' // marker, lọc message khác
type: string // tên event hoặc command
payload?: any // dữ liệu kèm theo
}5.1 Lắng nghe event từ iframe (iframe → parent)
window.addEventListener('message', (e) => {
if (e.origin !== 'https://app.zorio.vn') return // verify origin
if (e.data?.source !== 'zorio-webphone') return // verify marker
switch (e.data.type) {
case 'registered':
console.log('Webphone đã sẵn sàng')
break
case 'incoming_call':
console.log('Cuộc gọi đến:', e.data.payload.call.number)
myUI.showIncomingBanner(e.data.payload)
break
case 'call_answered':
myUI.openCallControls(e.data.payload.call.uuid)
break
case 'call_hangup':
myUI.closeCallControls()
console.log('Billsec:', e.data.payload.billsec)
break
case 'error':
console.error('Webphone error:', e.data.payload)
break
}
})Iframe forward đầy đủ 12 events giống SDK npm/UMD — xem Events.
5.2 Điều khiển webphone (parent → iframe)
const iframe = document.querySelector('iframe')
function sendCommand(type, payload) {
iframe.contentWindow.postMessage(
{ source: 'zorio-webphone', type, payload },
'https://app.zorio.vn' // target origin — KHÔNG dùng '*'
)
}
// Click-to-call từ button trong CRM
sendCommand('call', {
number: '0912345678',
options: { campaignId: 36, leadId: 78912 },
})
// Hangup cuộc đang nói
sendCommand('hangup', {})
// Gửi DTMF
sendCommand('send_dtmf', { digits: '1' })
// Mute / unmute
sendCommand('mute', {})
sendCommand('unmute', {})
// Refresh token sắp hết hạn
sendCommand('refresh_token', { token: newEmbedToken })Danh sách type command tương ứng 1-1 với 12 method API.
5.3 Ví dụ tích hợp đầy đủ
<div id="phone-container">
<iframe
id="zorio-frame"
src="https://app.zorio.vn/webphone-embed?token=PLACEHOLDER"
allow="microphone"
style="border:0; width:380px; height:560px"
></iframe>
</div>
<button id="btn-call">Gọi 0912345678</button>
<script>
const ZORIO_ORIGIN = 'https://app.zorio.vn'
const iframe = document.getElementById('zorio-frame')
// 1. Lấy embed token rồi inject
fetch('/api/internal/zorio-embed-token').then(r => r.json()).then(({ token }) => {
iframe.src = `${ZORIO_ORIGIN}/webphone-embed?token=${token}&layout=docked`
})
// 2. Lắng nghe events
window.addEventListener('message', (e) => {
if (e.origin !== ZORIO_ORIGIN) return
if (e.data?.source !== 'zorio-webphone') return
console.log('[zorio]', e.data.type, e.data.payload)
})
// 3. Điều khiển
document.getElementById('btn-call').onclick = () => {
iframe.contentWindow.postMessage(
{ source: 'zorio-webphone', type: 'call', payload: { number: '0912345678' } },
ZORIO_ORIGIN
)
}
</script>6. Tính năng v7 vẫn hoạt động trong iframe
Iframe build từ cùng bundle SDK, nên đầy đủ:
- Auto-reconnect supervisor — không cần xử lý reconnect ở parent.
- Pagehide cleanup — khi user đóng tab CRM, iframe unregister sạch.
- Early media — outbound nghe ringback ngay từ 183 Session Progress.
7. Khi nào nên iframe vs SDK npm/UMD?
| Tiêu chí | Iframe | SDK npm/UMD |
|---|---|---|
| Tốc độ tích hợp | ✅ Nhanh nhất (1 thẻ <iframe>) | Trung bình |
| Custom UI | ❌ UI mặc định của Zorio | ✅ Toàn quyền |
| Cross-origin | ✅ Cách ly hoàn toàn | Cần CSP setup |
| Tùy biến events nâng cao | postMessage | ✅ Native callback |
| Air-gap | Cần host iframe nội bộ | ✅ Self-host UMD |
Khuyến nghị
Nếu đối tác chỉ cần "có cái webphone" trên CRM trong một ngày → dùng iframe. Nếu cần custom UX hoặc tích hợp sâu vào workflow CRM (CTI, screen-pop) → dùng npm hoặc UMD.
Tham khảo thêm:
