ฝังหน้าจอง Keeprer ลงเว็บไซต์ของคุณ
หนึ่ง <script> วางที่ไหนก็ได้บนหน้าเว็บ แล้วลูกค้าจองเวลาได้ทันทีโดยไม่ต้องออกจากเว็บคุณ
คำขอทุกอันวิ่งเข้า inbox ของ Keeprer พร้อมผ่านการคัดกรองด้วย AI และร่างอีเมลตอบให้แล้ว
ไม่ต้องใช้ API key · ไม่ต้องตั้ง CORS · ไม่ต้องมี backend · ไม่มี dependency ให้ติดตั้ง
ภาพรวม
embed คือ iframe ของหน้าจองจริงของคุณ ไม่ใช่ widget ที่ยัด DOM เข้าไปในหน้าเว็บ เลือกแบบนี้เพราะสองเหตุผล:
- CSS ของเว็บคุณกับของ Keeprer ไม่ชนกัน — คนละ document แยกขาดกันสมบูรณ์ ไม่ต้องกลัว reset หรือ utility class ตีกัน
- ไม่ต้องเปิด CORS — การส่งคำขอจองเกิดขึ้นข้างใน iframe ซึ่งอยู่บนโดเมนของ Keeprer เอง endpoint สาธารณะจึงยัง same-origin เหมือนเดิม กติกาฝั่ง server (บังคับ
status=pendingเสมอ, rate limit ต่อ IP) ไม่ถูกแตะเลย
ติดตั้งใน 3 นาที
-
เอา snippet มา — เข้า Keeprer → Share → Embed on your site
ต้องกรอกโดเมนก่อนถึงจะเห็นช่อง snippet เพราะรายการโดเมนว่าง = ปิดการฝังอยู่
-
ใส่โดเมนของเว็บที่จะฝัง บรรทัดละ 1 อัน แล้วกด Save domains
https://yoursite.com https://www.yoursite.com http://localhost:3000ใส่ให้ครบทุกโดเมนที่ใช้จริง www กับไม่ใส่ www นับเป็นคนละโดเมน
-
วาง snippet ตรงที่อยากให้หน้าจองโผล่
<script src="https://keeprer.space/embed.js" data-keeprer="your-slug"></script>
เท่านั้นจริงๆ ไม่ต้องทำกรอบหรือการ์ดมารองรับ — พื้นหลังของ embed โปร่งใส วางลงบนพื้นหลังแบบไหนก็กลืน
ทำงานยังไง
embed.jsอ่านdata-keeprerแล้วสร้าง<iframe>วางแทนตัวเองตรงจุดที่ script tag อยู่- iframe ชี้ไปที่
/b/<slug>?embed=1บนโดเมน Keeprer - server ตอบกลับพร้อม header
Content-Security-Policy: frame-ancestors …ที่สร้างจากรายการโดเมนที่คุณบันทึกไว้ เบราว์เซอร์เป็นคนบังคับใช้ ถ้าเว็บที่ฝังไม่อยู่ในรายการก็จะถูกบล็อกทันที - หน้าจองเรนเดอร์แล้วส่งความสูงของตัวเองกลับมาให้หน้าเว็บแม่ผ่าน
postMessageเพื่อปรับ iframe ให้พอดี ไม่มี scrollbar ซ้อน - ลูกค้ากดจอง → POST เกิดขึ้นใน iframe (same-origin) → คำขอเข้า inbox ผ่าน AI screening → คุณได้อีเมลและ push แจ้งเตือน
Attribute ของ script tag
| Attribute | จำเป็น | ค่าเริ่มต้น | ทำอะไร |
|---|---|---|---|
src | ใช่ | — | URL ของ embed.js โดเมนใน src นี้จะถูกใช้เป็นโดเมนของ iframe ด้วย |
data-keeprer | ใช่ | — | slug ของหน้าจอง (ส่วนท้าย URL หน้าจองของคุณ) |
data-height | ไม่ | 620 | ความสูงเริ่มต้นเป็น px ก่อนที่ auto-resize จะส่งค่าจริงมา ตั้งให้ใกล้ของจริงจะลดการกระตุกตอนโหลด |
iframe ที่สร้างขึ้นจะได้ loading="lazy" และ allow="microphone" อัตโนมัติ
(อย่างหลังจำเป็นสำหรับฟีเจอร์ฝากข้อความเสียง — iframe ข้ามโดเมนจะใช้ไมค์ได้ต่อเมื่อหน้าเว็บแม่มอบสิทธิ์ให้)
Endpoint ที่เกี่ยวข้อง
| Method | Path | ต้อง auth | หมายเหตุ |
|---|---|---|---|
| GET | /embed.js | ไม่ | ตัว loader |
| GET | /b/:slug?embed=1 | ไม่ | หน้าที่ถูกฝัง ส่ง frame-ancestors ตามรายการโดเมน |
| POST | /api/page/:slug/book | ไม่ | ส่งคำขอจอง — เรียกได้จากใน iframe เท่านั้น (same-origin) |
| POST | /api/page/:slug/voicemail | ไม่ | ฝากข้อความเสียง |
| PUT | /api/me/page | ใช่ | ตั้งรายการโดเมนด้วยฟิลด์ embed_origins: string[] |
Access-Control-Allow-Origin เลย
/api/page/:slug/book จึงเรียกจากเซิร์ฟเวอร์หรือ JS ของคุณเองไม่ได้ — ต้องผ่าน iframe เท่านั้น
นี่เป็นการออกแบบโดยตั้งใจ ไม่ใช่ของที่ยังไม่ได้ทำ: ถ้าเปิด CORS ให้ endpoint นี้ มันจะกลายเป็น open relay ที่ใครก็ยิงเข้ามาได้ทันที
กติกาการใส่โดเมน
| ใส่แบบนี้ | ระบบเก็บเป็น | หมายเหตุ |
|---|---|---|
acme.com | https://acme.com | ไม่ใส่ scheme = เดาเป็น https |
acme.com/pricing | https://acme.com | path/query ถูกตัดทิ้ง เพราะ frame-ancestors ดูแค่โดเมน |
localhost:3000 | http://localhost:3000 | loopback เดาเป็น http เพราะ dev server แทบทั้งหมดเป็น http |
https://acme.com:8443 | https://acme.com:8443 | พอร์ตนับเป็นส่วนหนึ่งของโดเมน |
*.acme.com | ถูกปฏิเสธ | wildcard เปิดให้ทุก subdomain รวมถึงอันที่โดนยึด ฝังหน้าคุณได้ |
- สูงสุด 10 โดเมน ต่อหนึ่งหน้าจอง
- scheme ต้องตรงเป๊ะ —
https://acme.comไม่ครอบคลุมhttp://acme.com - www สำคัญ —
acme.comกับwww.acme.comเป็นคนละโดเมนในสายตาเบราว์เซอร์ - รายการว่าง = ปิดการฝัง (เป็นค่าเริ่มต้นของทุกหน้าจอง)
postMessage ที่ iframe ส่งออกมา
ถ้าอยากรู้ความสูงของ embed เพื่อไปทำอย่างอื่นต่อ ดักฟังได้ — แต่ต้องตรวจ origin ทุกครั้ง
window.addEventListener('message', (e) => {
if (e.origin !== 'https://keeprer.space') return; // ห้ามข้ามบรรทัดนี้
if (e.data?.type === 'keeprer:height') {
console.log('ความสูงของ embed:', e.data.h);
}
});
ตอนนี้มีข้อความชนิดเดียวคือ keeprer:height โดยมีแค่ตัวเลขข้ามฝั่งมา ไม่มีข้อมูลผู้จองใดๆ
ฝั่ง embed.js ก็ตรวจทั้ง e.origin และ e.source ก่อนปรับความสูง
ใส่ใน React / Next.js / Vue
script tag ธรรมดาจะไม่ทำงานถ้าใส่ผ่าน JSX ตรงๆ เพราะ React ไม่รัน script ที่ inject เข้ามา วิธีที่ถูกคือสร้าง element เอง
React / Next.js (client component)
'use client';
import { useEffect, useRef } from 'react';
export function KeeprerEmbed({ slug, height = 620 }) {
const box = useRef(null);
useEffect(() => {
const el = box.current;
if (!el) return;
const s = document.createElement('script');
s.src = 'https://keeprer.space/embed.js';
s.dataset.keeprer = slug;
s.dataset.height = String(height);
el.appendChild(s);
// React 18 StrictMode รัน effect สองรอบตอน dev — เคลียร์ทิ้งกัน embed ซ้อน
return () => { el.innerHTML = ''; };
}, [slug, height]);
return <div ref={box} />;
}
Vue 3
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
const box = ref(null);
const props = defineProps({ slug: String });
onMounted(() => {
const s = document.createElement('script');
s.src = 'https://keeprer.space/embed.js';
s.dataset.keeprer = props.slug;
box.value.appendChild(s);
});
onUnmounted(() => { if (box.value) box.value.innerHTML = ''; });
</script>
<template><div ref="box" /></template>
WordPress / Wix / Squarespace / Webflow
ใช้บล็อกชนิด Custom HTML / Embed code แล้ววาง snippet ลงไปตรงๆ อย่าวางในบล็อกข้อความธรรมดาเพราะระบบจะ escape tag ทิ้ง
จัดวางและสไตล์
- ความกว้าง — iframe กว้าง 100% ของ element แม่เสมอ คุมความกว้างที่ container ของคุณ
- ความสูง — ปรับอัตโนมัติ ไม่ต้องเซ็ต อย่าใส่ CSS
heightทับ เพราะจะทำให้เนื้อหาโดนตัด - พื้นหลัง — โปร่งใส พื้นหลังของเว็บคุณจะทะลุขึ้นมา ไม่ต้องทำการ์ดมารอง
- ธีมและสี — ปรับที่ Keeprer → Bookpage → Appearance (สว่าง/มืด + สีแบรนด์) ไม่ได้ตั้งจากฝั่งเว็บคุณ
- จอแคบ — iframe มี viewport ของตัวเอง พอกว้างน้อยกว่า 900px เลย์เอาต์จะยุบเป็นคอลัมน์เดียวเองอัตโนมัติ แม้หน้าจอจริงจะกว้างก็ตาม
sandbox โดยไม่มี allow-scripts allow-same-origin allow-forms
หน้าจองจะไม่ทำงาน แนะนำให้ใช้ embed.js ตามปกติดีกว่า
แก้ปัญหา
กรอบว่าง + console ขึ้น “violates … frame-ancestors”
โดเมนของเว็บคุณไม่อยู่ในรายการ อ่านข้อความ error ให้ดี มันบอกทั้งโดเมนที่คุณใช้จริงและรายการที่บันทึกไว้
Framing 'https://www.acme.com/' violates the following Content Security Policy
directive: "frame-ancestors https://acme.com". The request has been blocked.
เทียบสองค่านี้ทีละตัวอักษร สาเหตุที่เจอบ่อยเรียงตามความถี่: ขาด www · http ปะทะ https · ลืมพอร์ต
ขึ้นว่า “Embedding isn’t enabled for this booking page”
รายการโดเมนยังว่าง หรือ slug ผิด ไปกรอกโดเมนที่ Share → Embed on your site แล้วกด Save
ขึ้นว่า “This booking page isn’t published”
หน้าจองยังเป็นฉบับร่าง เปิด publish ที่แท็บ Bookpage ก่อน
ไม่มีอะไรโผล่เลย ไม่มี error
เช็คว่า script tag ยังอยู่ใน DOM จริง (บาง CMS ตัด <script> ทิ้ง) และมี data-keeprer
ถ้าลืม attribute นี้ console จะเตือนว่า [keeprer] missing data-keeprer
ความสูงไม่ยอมปรับ
มักเกิดจาก CSS ของเว็บคุณไปทับ เช่น iframe { height: 400px !important }
ซึ่ง theme สำเร็จรูปหลายตัวมี ลองตรวจด้วย DevTools ที่ตัว iframe
ปุ่มอัดเสียงกดแล้วไม่ทำงาน
เบราว์เซอร์ให้สิทธิ์ไมค์เฉพาะหน้าเว็บที่เป็น HTTPS เท่านั้น (ยกเว้น localhost) ถ้าเว็บคุณยังเป็น http จะใช้ไม่ได้
Prompt สำหรับสั่ง AI agent
ถ้าจะให้ AI agent (Claude Code, Cursor, Copilot ฯลฯ) ติดตั้งให้ ก๊อปข้อความข้างล่างไปวางได้เลย แก้แค่ 3 ค่าที่วงเล็บเหลี่ยมไว้
ช่วยฝังหน้าจอง Keeprer ลงในเว็บนี้ให้หน่อย
ค่าที่ต้องใช้:
- slug ของหน้าจอง: [YOUR-SLUG]
- โดเมนของ Keeprer: https://keeprer.space
- หน้าที่จะให้ embed ไปโผล่: [เช่น หน้า Contact หรือ section "นัดหมาย" ในหน้าแรก]
สิ่งที่ต้องทำ:
1. สำรวจ codebase ก่อนว่าเป็น framework อะไร (React/Next/Vue/Astro/WordPress/HTML ล้วน)
แล้วค่อยเลือกวิธีใส่ให้เข้ากับโครงเดิม
2. ใส่ snippet นี้ตรงตำแหน่งที่ระบุไว้:
<script src="https://keeprer.space/embed.js" data-keeprer="[YOUR-SLUG]"></script>
- ถ้าเป็น React/Vue: script tag ใน JSX/template จะไม่ทำงาน ต้องสร้าง element
ด้วย document.createElement ใน useEffect/onMounted แล้ว appendChild
และต้องเคลียร์ตอน unmount กัน embed ซ้อนตอน StrictMode รัน effect ซ้ำ
- ถ้าเป็น HTML ล้วนหรือ CMS: วาง script tag ตรงๆ ในบล็อก Custom HTML
3. อย่าสร้าง wrapper/การ์ด/กรอบมาครอบ — พื้นหลังของ embed โปร่งใสอยู่แล้ว
คุมแค่ความกว้างของ container ที่ครอบอยู่ก็พอ
4. ห้ามใส่ CSS height ทับตัว iframe เด็ดขาด เพราะความสูงมันปรับเองผ่าน postMessage
ถ้าไปทับจะทำให้เนื้อหาโดนตัด — ถ้าเจอ CSS เดิมในโปรเจกต์ที่ตั้ง
iframe { height: ... } แบบเหมารวม ให้ยกเว้น iframe ตัวนี้ออกมา
5. ห้ามเพิ่ม dependency ใดๆ ห้ามเรียก API ของ Keeprer จากฝั่ง server
ไม่มี public REST API ข้ามโดเมน การจองเกิดขึ้นใน iframe เท่านั้น
เสร็จแล้วบอกผมด้วยว่า "ต้องเอาโดเมนไหนไปใส่ใน allow-list บ้าง"
โดยลิสต์ทุก origin ที่เว็บนี้เสิร์ฟจริง — ทั้ง production, staging และ dev
พร้อม scheme และพอร์ตให้ครบ (www กับไม่ใส่ www นับเป็นคนละอัน)
เพราะผมต้องเอาไปกรอกที่ Keeprer → Share → Embed on your site เอง
ถ้าไม่กรอก เบราว์เซอร์จะบล็อก iframe และขึ้นกรอบว่าง
สุดท้ายช่วยรันเว็บขึ้นมาแล้วยืนยันให้ด้วยว่า iframe โผล่จริง
ความสูงปรับเองได้ และไม่มี error ใน console
Checklist ก่อนขึ้นจริง
- หน้าจอง publish แล้ว
- ใส่โดเมน production ครบทั้งแบบมี www และไม่มี www
- ลองจองจริงหนึ่งครั้งจากเว็บที่ฝัง แล้วเช็คว่าเข้า inbox
- เปิดดูบนมือถือ — iframe ต้องยุบเป็นคอลัมน์เดียวและไม่มี scrollbar ซ้อน
- เปิด DevTools console ดูว่าไม่มี CSP error หลงเหลือ
- ถ้าใช้แพ็กเกจฟรี จะมีป้าย “Want a booking page?” แสดงใต้ฟอร์มบนเว็บคุณ — อัปเป็น Pro ถ้าไม่ต้องการ