Handle Booking Page with Scheduling
Use this guide when a developer needs to build a public handle page, such as https://arjun.10x.in, with a clear option for visitors to book time through native 10x Scheduling.
The general Scheduling guide already exists at Scheduling. This page is the implementation handoff for the handle-page use case: page structure, API sequence, booking link behavior, inline slot-picker behavior, and rollout checks.
Recommended Approach
For the first version, publish the handle page and link its primary booking CTA to the first-party Scheduling page:
https://app.10x.in/apps/public/scheduling/{handle}/{eventSlug}
Example:
https://app.10x.in/apps/public/scheduling/arjun/intro-call
This keeps the custom profile page simple while 10x owns slot selection, booking creation, payment redirect, cancellation, rescheduling, calendar sync, confirmation email, and webhook side effects.
If the design requires slots directly on arjun.10x.in, build an inline picker with the public Scheduling API. Keep a fallback link to the first-party Scheduling page for degraded states.
Page Structure
Use this structure for a handle profile or creator booking page:
handle: arjun
publicHost: https://arjun.10x.in
page:
path: /
primaryAction: book_time
scheduling:
eventSlug: intro-call
mode: link_first
publicBookingPath: /apps/public/scheduling/arjun/intro-call
fallbackLabel: Book a time
content:
headline: Work with Arjun
proof: selected outcomes, testimonials, or examples
offer: 30-minute intro call
expectation: what the visitor should prepare before booking
analytics:
source: handle_profile
tracking:
page: profile
placement: hero_cta
The public page should include:
| Section | Purpose |
|---|---|
| Identity | Handle owner name, role, and what visitors can book. |
| Proof | Outcomes, examples, testimonials, or trust signals. |
| Offer | Event title, duration, price if paid, and who the session is for. |
| Booking CTA | Link to /apps/public/scheduling/{handle}/{eventSlug} or inline slot picker. |
| Fallback | Lead form, email, or request-follow-up CTA if scheduling is unavailable. |
| Attribution | Source metadata for analytics or downstream follow-up. |
Integration Options
| Option | Best for | Implementation |
|---|---|---|
| Link-first CTA | Most pages and fastest launch | Button links to the first-party Scheduling page. |
| Inline picker | High-control branded pages | Page calls the public Scheduling API and creates bookings itself. |
| Hybrid | Recommended for important profiles | Show key times inline and keep a fallback link to the first-party page. |
Do not iframe the first-party Scheduling page unless the current deployment explicitly allows framing. Prefer a link or API-driven inline component.
Owner Setup
The handle owner or a creator with access sets up the event type in Product Suite, or an internal tool can call the owner APIs with a signed-in JWT.
Required owner access:
- Handle:
arjun - Role:
CREATORor higher - Auth: control-plane JWT
- Optional integrations: Google Calendar/Meet, Office365 Calendar/Video, CalDAV, Zoom, Stripe, custom
CALENDARsender profile
1. Create or Confirm Availability
If no schedule exists, Scheduling falls back to a default weekday schedule. Create an explicit schedule when the page needs a named time zone or custom hours.
export TENX_API_BASE="https://ai.10x.in"
export TENX_HANDLE="arjun"
export TENX_JWT="<creator-jwt>"
curl -sS -X POST "${TENX_API_BASE}/v2/handles/${TENX_HANDLE}/scheduling/schedules" \
-H "Authorization: Bearer ${TENX_JWT}" \
-H "Content-Type: application/json" \
-d '{
"scheduleId": "default",
"timeZone": "Asia/Kolkata",
"weeklyAvailability": [
{ "day": 1, "startTime": "10:00", "endTime": "17:00" },
{ "day": 2, "startTime": "10:00", "endTime": "17:00" },
{ "day": 3, "startTime": "10:00", "endTime": "17:00" },
{ "day": 4, "startTime": "10:00", "endTime": "17:00" },
{ "day": 5, "startTime": "10:00", "endTime": "17:00" }
],
"overrides": []
}'
day uses JavaScript weekday numbering: 0 is Sunday, 1 is Monday, and 6 is Saturday.
2. Create the Event Type Hidden
Create the event type as hidden first. Hidden or draft events are not public, so the public booking URL returns 404 until the event is published.
curl -sS -X POST "${TENX_API_BASE}/v2/handles/${TENX_HANDLE}/scheduling/event-types" \
-H "Authorization: Bearer ${TENX_JWT}" \
-H "Content-Type: application/json" \
-d '{
"slug": "intro-call",
"title": "30-minute intro call",
"description": "Use this call to scope the problem, decide next steps, and confirm whether 10x/OpenAnalyst is a fit.",
"durationMinutes": 30,
"timeZone": "Asia/Kolkata",
"scheduleId": "default",
"hidden": true,
"confirmationRequired": false,
"minimumNoticeMinutes": 120,
"buffers": {
"beforeMinutes": 10,
"afterMinutes": 10
},
"locations": [
{
"type": "google_meet",
"label": "Google Meet"
}
],
"bookingFields": [
{
"id": "topic",
"label": "What should we discuss?",
"type": "textarea",
"required": true
}
],
"payment": {
"enabled": false
}
}'
Save the returned eventType.eventTypeId. The public URL uses slug, but updates use eventTypeId.
3. Connect Calendars and Meeting Providers
Use Product Suite when possible. For API-driven setup:
| Job | Method and path |
|---|---|
| List providers and credentials | GET /v2/handles/{handle}/scheduling/integrations |
| Start OAuth | POST /v2/handles/{handle}/scheduling/integrations/{providerKey}/oauth/start |
| Complete OAuth | POST /v2/handles/{handle}/scheduling/integrations/{providerKey}/oauth/callback |
| Add CalDAV credential | POST /v2/handles/{handle}/scheduling/integrations/caldav/credentials |
| List provider calendars | GET /v2/handles/{handle}/scheduling/integrations/{providerKey}/calendars |
| Select busy calendars | POST /v2/handles/{handle}/scheduling/selected-calendars |
| Disconnect credential | DELETE /v2/handles/{handle}/scheduling/integrations/{providerKey}/credentials/{credentialId} |
Supported provider keys are google, office365, caldav, zoom, and stripe. Stripe uses the existing billing secret and only matters when the event type has payment.enabled: true.
4. Publish the Event Type
After QA confirms the event details, availability, calendar connection, and page CTA, publish the event by setting hidden to false.
export EVENT_TYPE_ID="<returned-event-type-id>"
curl -sS -X PUT "${TENX_API_BASE}/v2/handles/${TENX_HANDLE}/scheduling/event-types/${EVENT_TYPE_ID}" \
-H "Authorization: Bearer ${TENX_JWT}" \
-H "Content-Type: application/json" \
-d '{
"hidden": false
}'
The event becomes public only when it is not hidden and has status: "ACTIVE".
Link-First Handle Page
Use this when the page only needs a booking button.
<section class="profile-booking">
<p class="eyebrow">Available for consultations</p>
<h1>Work with Arjun</h1>
<p>
Book a 30-minute intro call to scope the problem, decide next steps, and
confirm whether a 10x or OpenAnalyst workflow is the right fit.
</p>
<a
class="button primary"
href="https://app.10x.in/apps/public/scheduling/arjun/intro-call"
>
Book a time
</a>
<a class="button secondary" href="/contact">
Request follow-up instead
</a>
</section>
Acceptance criteria:
- The CTA uses the correct
handleandeventSlug. - The public booking URL returns the event, not
404. - The first-party page shows slots in the expected time zone.
- Paid events redirect to Stripe and return to the Scheduling page after checkout.
- The handle page includes a fallback when no slots are available.
Inline Slot Picker
Use the public Scheduling API only when the handle page needs to show slots without sending the visitor to the first-party page first.
Public endpoints:
| Job | Method and path | Auth |
|---|---|---|
| List public event types | GET /v2/public/scheduling/{handle} | None |
| Read one event type | GET /v2/public/scheduling/{handle}/{eventSlug} | None |
| List slots | GET /v2/public/scheduling/{handle}/{eventSlug}/slots?start={iso}&end={iso} | None |
| Create booking | POST /v2/public/scheduling/{handle}/{eventSlug}/bookings | None |
| Cancel booking | POST /v2/public/scheduling/{handle}/{eventSlug}/bookings/{bookingUid}/cancel | None |
| Reschedule booking | POST /v2/public/scheduling/{handle}/{eventSlug}/reschedule | None |
Load Event Metadata
curl -sS "${TENX_API_BASE}/v2/public/scheduling/arjun/intro-call"
Expected shape:
{
"event": {
"handle": "arjun",
"eventTypeId": "evt_...",
"slug": "intro-call",
"title": "30-minute intro call",
"description": "Use this call to scope the problem...",
"duration": 30,
"locations": [
{
"type": "google_meet",
"label": "Google Meet"
}
],
"timeZone": "Asia/Kolkata",
"bookingFields": [],
"paymentRequired": false,
"availableActions": ["book", "cancel", "reschedule"]
}
}
Load Available Slots
curl -sS "${TENX_API_BASE}/v2/public/scheduling/arjun/intro-call/slots?start=2026-06-08T00:00:00.000Z&end=2026-06-15T00:00:00.000Z"
Expected shape:
{
"slots": [
{
"start": "2026-06-09T05:00:00.000Z",
"end": "2026-06-09T05:30:00.000Z",
"timeZone": "Asia/Kolkata",
"sourceAvailability": {
"kind": "weekly",
"label": "2:10:00-17:00"
},
"bookingNonce": "slot_..."
}
],
"displayTimeZone": "Asia/Kolkata"
}
Create a Booking
curl -sS -X POST "${TENX_API_BASE}/v2/public/scheduling/arjun/intro-call/bookings" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: arjun-intro-20260609T050000Z-lead@example.com" \
-d '{
"attendee": {
"name": "Rishabh",
"email": "lead@example.com",
"timeZone": "Asia/Kolkata"
},
"slot": {
"start": "2026-06-09T05:00:00.000Z",
"end": "2026-06-09T05:30:00.000Z"
},
"answers": {
"topic": "We want to discuss an AI sales workflow."
},
"guests": [],
"tracking": {
"source": "handle_profile",
"placement": "hero_cta"
}
}'
Possible booking statuses:
| Status | Meaning |
|---|---|
ACCEPTED | Confirmed immediately. Calendar/video side effects are attempted. |
PENDING | Saved, but owner confirmation is required. |
PENDING_PAYMENT | Saved, but Stripe checkout must complete before confirmation. |
CANCELLED | Booking was cancelled. |
REJECTED | Booking was rejected or checkout failed before confirmation. |
If the response includes:
{
"nextAction": {
"type": "redirect_to_checkout",
"url": "https://checkout.stripe.com/..."
}
}
redirect the visitor to nextAction.url.
Browser Skeleton
<div
id="tenx-inline-scheduler"
data-api-base="https://ai.10x.in"
data-handle="arjun"
data-event-slug="intro-call"
></div>
The skeleton intentionally omits styling and rendering. The developer should render slot buttons, validate name/email before booking, display error states, and handle nextAction.redirect_to_checkout.
Webhooks and Follow-Up
Use Scheduling webhooks when a booking should notify a CRM, Slack workflow, operator queue, or custom fulfillment system.
Owner API:
POST /v2/handles/{handle}/scheduling/webhooks
Supported events:
scheduling.booking.createdscheduling.booking.confirmedscheduling.booking.cancelledscheduling.booking.rescheduledscheduling.payment.requiredscheduling.payment.succeededscheduling.sync.degraded
For delivery behavior, see Webhook Delivery and Failure Handling.
Rollout Checklist
Before handing the page to a visitor:
- Confirm the handle host resolves, for example
https://arjun.10x.in. - Confirm the page CTA uses the intended
handleandeventSlug. - Confirm
GET /v2/public/scheduling/{handle}/{eventSlug}returns200. - Confirm slots appear for the next seven days.
- Create a test booking with a real email address.
- For paid events, complete a Stripe checkout and confirm the booking moves from
PENDING_PAYMENTtoACCEPTED. - For connected calendars, confirm unavailable external calendar blocks are removed from the slot list.
- Confirm booking confirmation, reschedule, and cancellation emails use the intended sender.
- Confirm any webhooks receive the expected
scheduling.*payloads. - Confirm the handle page has a fallback lead form or contact CTA when Scheduling is unavailable.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Public booking URL returns 404 | Event is still hidden, not active, or the slug is wrong. | Publish the event with hidden: false and verify the slug. |
| Slots are empty | Weekly availability, date overrides, minimum notice, buffers, or selected calendars remove all slots. | Check the event schedule and busy-calendar selections. |
Booking returns booking_required_fields | Missing attendee name, attendee email, slot start, or slot end. | Validate the inline form before posting. |
Booking returns scheduling_slot_unavailable | Another booking reserved the slot or the requested slot was not generated by availability. | Reload slots and ask the visitor to choose again. |
| Paid booking does not confirm | Stripe checkout did not complete or the webhook completion was not processed. | Inspect the booking, checkout session, and scheduling.payment.* webhook events. |
| Calendar sync is degraded | Provider side effects failed after the booking was saved. | Keep the booking and reconnect the provider or handle follow-up manually. |
Related:
Updated Jun 19, 2026
