Embedded Scheduler BETA
The power of the Scheduler can be embedded into your application, providing world-class scheduling and integrating with your workflows.
The Scheduler Embed is set up in two steps:
- Generate an Embed Secret and public key and use them to generate Embed Tokens
- Include the Embed Token and Embedded Scheduling markup on your page
Once set up, your users can use the embedded Scheduler to generate request links.
You can integrate these into your application’s UI and also receive Webhooks when the state of the request changes via your servers.
Enabling the Scheduler Embed for your application #
The Scheduler Embed is an optional feature and must be enabled first.
To enable the feature visit the Features section for your application in the Cronofy developer dashboard, ensure that “Enable Embed Credentials for this application” is toggled on, then click “Save core features”.
Installation #
The Cronofy embedded scheduler is available from the cronofy-scheduler-embed NPM package. You can install it by running in your terminal:
npm i -S cronofy-scheduler-embed
In your front end JavaScript code you must then import the embedded scheduler library before you can use the embeddable elements and APIs. This can be done by importing the scheduler library module within your JavaScript application:
import "cronofy-scheduler-embed";
Generating an Embed Secret #
- First ensure that you have enabled the Scheduler Embed for your application
- In your Cronofy Developer Dashboard, go to the “Credentials” section of your application
- Click the “Generate a new embed secret” button
- Store the public key and embed secret in your applications secrets so they can be passed to the function for generating a Embed Tokens when rendering the page.
Generating Embed Tokens #
The Embed Token that is used in your pages is a JWT (JSON Web Token) signed with your embed secret key.
Embed tokens should be hashed with the HS256 algorithm and signed with your Embed Secret, which begins ESK_
Example token generation code #
# Using the 'jwt' gem: https://github.com/jwt/ruby-jwt
embed_public_key = ENV.fetch("CRONOFY_SCHEDULER_EMBED_PUBLIC_KEY") # Value starting "EMB_"
embed_secret = ENV.fetch("CRONOFY_SCHEDULER_EMBED_SECRET") # Value starting "ESK_"
jwt_payload = {
iss: embed_public_key,
iat: Time.now.to_i,
exp: Time.now.to_i + (60 * 60 * 4),
aud: "scheduler_embed",
jti: SecureRandom.uuid,
}
embed_token = JWT.encode(jwt_payload, embed_secret, 'HS256')
// Use the firebase JWT library: https://github.com/firebase/php-jwt
use Firebase\JWT\JWT;
$embed_public_key = $_ENV["CRONOFY_SCHEDULER_EMBED_PUBLIC_KEY"]; // Value starting "EMB_"
$embed_secret = $_ENV["CRONOFY_SCHEDULER_EMBED_SECRET"]; // Value starting "ESK_"
$jwt_payload = [
"iss" => $embed_public_key,
"iat" => time(),
"exp" => time() + (60 * 60 * 4),
"aud" => "scheduler_embed",
"jti" => uniqid(),
// optional onboarding parameters: https://docs.cronofy.com/developers/embedded-scheduler/onboarding-users/#enhanced-embedded-onboarding-flow
"tenant_id" => "your-customer-id",
"tenant_name" => "Evenitron",
];
$embed_token = JWT::encode($jwt_payload, $embed_secret, "HS256");
// Use the auth0 JWT library: https://github.com/auth0/node-jsonwebtoken
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const embed_public_key = process.env["CRONOFY_SCHEDULER_EMBED_PUBLIC_KEY"]; // Value starting "EMB_"
const embed_secret = process.env["CRONOFY_SCHEDULER_EMBED_SECRET"]; // Value starting "ESK_"
const timestamp = Date.now() / 1000;
const embed_token = jwt.sign(
{
iss: embed_public_key,
iat: timestamp,
exp: timestamp + (60 * 60 * 4),
aud: "scheduler_embed",
jti: crypto.randomUUID(),
// optional onboarding parameters: https://docs.cronofy.com/developers/embedded-scheduler/onboarding-users/#enhanced-embedded-onboarding-flow
tenant_id: "your-customer-id",
tenant_name: "Evenitron"
},
embed_secret,
{
algorithm: "HS256"
}
);
using System;
// dotnet add package System.IdentityModel.Tokens.Jwt
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
var embedPublicKey = "EMB_...";
var embedSecret = "ESK_...";
var claims = new Claim[] {
new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer32),
new Claim("jti", Guid.NewGuid().ToString()),
// optional onboarding parameters: https://docs.cronofy.com/developers/embedded-scheduler/onboarding-users/#enhanced-embedded-onboarding-flow
new Claim("tenant_id", "your-customer-id"),
new Claim("tenant_name", "Evenitron")
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(embedSecret));
var signingCredentials = new SigningCredentials(key, "HS256");
JwtSecurityToken jwt = new JwtSecurityToken(
issuer: embedPublicKey,
audience: "scheduler_embed",
claims: claims,
expires: DateTime.UtcNow.AddHours(4),
signingCredentials: signingCredentials
);
var signedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
Payload values #
The following claims should be specified in the payload of your JWT:
iss required #
Your Embed public key, the value beginningEMB_
iat required #
The time the JWT was issued at, as an integer of seconds since the epoch (1970/01/01). The represented time cannot be in the future.exp required #
The time the JWT was expires at, as an integer of seconds since epoch (1970/01/01). The represented time must be in the future.This can be used to limit the time a button on your page works without needing a page refresh. The maximum value accepted is 4 hours after being issued.
aud required #
The audience of the JWT - this should always be the valuescheduler_embed
jti required #
The ID of the JWT - this is a value used to salt the signing of the JWT and prevent replay attacks, and should be unique to each JWT generated. We recommend using a UUID or GUID.tenant_id optional #
Provide this value to give users an improved onboarding experience. Read more here.tenant_name optional #
Provide this value to give users an improved onboarding experience. Read more here.sub optional #
Embedding the Scheduler in your page #
Once you have installed the embedded Scheduler library you can then include the cronofy-scheduler-button
element within your pages HTML:
<cronofy-scheduler-button
id="my-scheduler-button"
embed-token="{EMBED_TOKEN}"
correlation-id="event-1234"
recipient-email="doc@evenitron.com"
recipient-name="Dr Emmet Brown"
recipient-organization-name="Evenitron"
event-summary="Driving lessons - Marty & Doc"
event-duration-minutes="45"
></cronofy-scheduler-button>
The following element attributes are supported to setup and customize the initial state of the Scheduler:
embed-token required #
The JWT generated for each page view, as described in the “Generating Embed Tokens” section above.correlation-id required #
Your ID for the request, which can be used for reconciling future webhooks and API responses.recipient-email recommended #
The email address of the recipient of the scheduling request.This will be the email address of the person selecting the time. This address will be invited to the calendar event created when a time is chosen.
recipient-name optional #
The name of the recipient.recipient-organization-name optional #
The organization the recipient is representing.event-summary optional #
The summary of the event, which appears as the name of the event in the calendar.If this is not provided, the defaults for the user will be used.
event-duration-minutes optional #
Sets the value for the duration to the number of minutes specified.Receiving the request link in your UI #
The cronofy-scheduler-button
element emits events which your application can listen for.
You can register the event listener using addEventListener
:
document
.getElementById("#my-scheduler-button")
.addEventListener("cronofyschedulerrequestcreated", function(event) {
console.log("Cronofy Scheduler request created", event);
});
The event emitted contains details of the request in the detail
attribute:
{
"scheduling_request": {
"scheduling_request_id": "srq_52b65401a5d87bb4f0bee44e",
"slot_selection": "pending",
"primary_select_url": "https://app.cronofy.com/r/234ebnd",
"dashboard_url": "https://app.cronofy.com/scheduler/requests/52b65401a5d87bb4f0bee44e",
"summary": "Driving test - Marty & Doc",
"duration": { "minutes": 30 },
"recipient_operations": {
"view_url": "https://app.cronofy.com/rts/VFbnUVCv"
},
"recipients": [
{
"email": "doc@evenitron.com",
"display_name": "Dr Emmet Brown",
"slot_selector": true,
"select_url": "https://app.cronofy.com/r/234ebnd"
}
],
"event": {
"summary": "Driving test - Marty & Doc"
}
}
}
UI state events #
The cronofy-scheduler-button
element will also emit
the following events for closer UI integration:
cronofyschedulerrequestcreated
- When the Scheduling request has been createdcronofyschedulerdialogopened
- When Scheduler dialog is openedcronofyschedulerdialogclosed
- When Scheduler dialog is closedcronofyschedulererror
- When the Scheduler encountered an error, such as the users account being disabled or when the auth token is invalid. See a more detailed summary here.
You can register the event listener using addEventListener
:
document
.getElementById("#my-scheduler-button")
.addEventListener("cronofyschedulerdialogopened", function(event) {
console.log("Cronofy Scheduler dialog opened", event);
});
Element methods and properties #
The SchedulerButtonElement
provides several methods and properties that can be called from JavaScript to inspect the current state of the Scheduler dialog, as well as methods to open and close the popup. We have kept these close to the HTML spec for the Dialog element.
open #
A Boolean
value indicating whether a dialog is currently open or not.
show() #
Displays the Scheduler dialog as a modal. Note that if the dialog is already open then calling this method does nothing.
close() #
Closes the Scheduler dialog if it is open. Note that if the dialog is already closed then calling this method does nothing.
Embedding the cancel button in your page #
Once you have installed the embedded Scheduler library you can then include the cronofy-scheduler-cancel-button
element within your pages HTML:
<cronofy-scheduler-cancel-button
id="my-cancel-button"
embed-token="{EMBED_TOKEN}"
scheduling-request-id="{SCHEDULING_REQUEST_ID}"
></cronofy-scheduler-cancel-button>
The following element attributes are supported to setup and customize the initial state of the Scheduler:
embed-token required #
The JWT generated for each page view, as described in the “Generating Embed Tokens” section above.scheduling-request-id required #
The ID of the scheduling request to be cancelled.UI state events #
The cronofy-scheduler-cancel-button
element will also emit
the following events for closer UI integration:
cronofyschedulerrequestcancelled
- When the Scheduling request has been cancelledcronofyschedulerdialogopened
- When cancel dialog is openedcronofyschedulerdialogclosed
- When cancel dialog is closedcronofyschedulererror
- When the cancel dialog encountered an error, such as the users account being disabled or when the auth token is invalid. See a more detailed summary here.
You can register the event listener using addEventListener
:
document
.getElementById("#my-cancel-button")
.addEventListener("cronofyschedulerdialogopened", function(event) {
console.log("Cronofy cancel Scheduling request dialog opened", event);
});
Element methods and properties #
The SchedulerCancelButtonElement
provides several methods and properties that can be called from JavaScript to inspect the current state of the dialog, as well as methods to open and close the popup. We have kept these close to the HTML spec for the Dialog element.
open #
A Boolean
value indicating whether a dialog is currently open or not.
show() #
Displays the cancel dialog as a modal. Note that if the dialog is already open then calling this method does nothing.
close() #
Closes the cancel dialog if it is open. Note that if the dialog is already closed then calling this method does nothing.
Receiving updates to your servers #
You can set a URL to receive webhook Push Notifications to in the Cronofy Developer Dashboard where the Embed Secret was set earlier:
- Go to the “Credentials” section of your application settings
- Add the URL in the “Embed webhook callback URL” field and press “Save webhook URL”
This will begin sending Push Notifications for any of your Scheduling Requests to the given URL. You can disable these Push Notifications by updating the field to an empty value.
All push notifications sent from Cronofy include a HMAC header described in the Push Notifications section. This header can be used to verify the notification was sent by Cronofy and has not been tampered with.
Push notification example #
{
"notification": {
"type": "scheduling_request_time_chosen",
"triggered_at": "2024-10-30T16:14:11Z"
},
"scheduling_request": {
"scheduling_request_id":"srq_80f7fece4f37897d62d86d80",
"slot_selection": "complete",
"primary_select_url": "https://app.cronofy.com/rts/LOiwlKXQ",
"dashboard_url": "https://app.cronofy.com/scheduler/80f7fece4f37897d62d86d80",
"recipient_operations": {
"view_url": "https://app.cronofy.com/rts/LOiwlKXQ",
"reschedule_url": "https://app.cronofy.com/rts/LOiwlKXQ?reschedule=true",
"decline_url": "https://app.cronofy.com/rts/LOiwlKXQ?decline=true"
},
"summary": "Product Manager Interview at Globex",
"duration": { "minutes": 30 },
"recipients": [
{
"email":"eb@example.com",
"display_name":"Dr Emmet Brown",
"select_url":"https://app.cronofy.com/rts/1234abc",
"slot_selector": true
}
],
"event": {
"summary":"Product Manager Interview at Globex",
"metadata": {
"scheduler": {
"correlation_id": "foobar",
"utm_medium": "email",
"utm_source": "automated_email_workflow",
"utm_campaign": "candidate_interview_offer_email",
"captured_data.phone_number": "0118999881999119725 3",
"captured_data.notes": "No dietary requirements."
}
}
}
}
}
The notifications types are:
scheduling_request_time_chosen
- When an initial time slot is selected via the linkscheduling_request_rescheduled
- When a Request is rescheduled by the recipient via the linkscheduling_request_cancelled
- When a Request is cancelledscheduling_request_declined
- When a Request is declined by the recipientscheduling_request_host_declined
- When a Request is declined by the hostscheduling_request_recipient_unavailable
- When the recipient clicks the “I can’t do any of those days” button on the slot selection page, indicating they aren’t available for any of the offered times.scheduling_request_more_times_requested
- When no availability can be found for this request and the recipient presses the “Request more times” button to ask the host to make more times available.
notification.triggered_at
is the timestamp of when the Push Notification was triggered. If Cronofy receives a HTTP error status when trying to send your application a Push Notification, it will backoff and retry. Failed notifications will be retried for several hours, so this timestamp can be used to establish notifications which have been superseded already.
The schema of scheduling_request
can be found in Scheduling Request query documentation.
Custom styling #
Internally the cronofy-scheduler-button
element uses the shadow DOM to fully separate the internal styles and HTML from your own application. You can use the CSS ::part
pseudo-element to apply custom styling to both the button and popup window to make their look and feel match your application.
::part(button) #
The button
part selects the <button>
component. You can apply colors, text colors, border styles, padding, and other CSS properties.
The selector also works when combined with CSS pseudo-classes such as :hover
or :disabled
.
#my-scheduler-button::part(button) {
color: white;
background-color: #ff0044;
border: none;
border-radius: 4px;
padding: 6px 12px;
}
#my-scheduler-button::part(button):hover {
color: black;
background-color: #d3d3d3;
}
::part(dialog) #
The dialog
part is used to select the popup that is displayed when the button is clicked. You can change properties such as the dialogs width and height, alter the border radius, add padding, and other CSS properties. Note that the Scheduler form itself is not stylable and will be displayed with a white background.
#my-scheduler-button::part(dialog) {
border-radius: 4px;
width: 80vw;
height: 80dvh;
}
Frequently Asked Questions #
Server Side Rendering (SSR) Caveats #
The cronofy-scheduler-button
is implemented as a web component. This makes the component framework agnostic however it does require the DOM to work. Some JavaScript frameworks that perform server side rendering (such as NextJS, Nuxt, and SvelteKit) may produce an error such as HTMLElement is not defined
when trying to server side render the scheduler button element.
In order to work around this you will need to disable SSR for the button component. This is usually achieved by moving the button into its own component and tagging it as client renderable only. This is done with the "use client"
directive in NextJS, the <ClientOnly>
property in Nuxt, and by using export const ssr = false;
in SvelteKit.
Component TypeScript definition for React #
If you are using React with TypeScript as your frontend stack you will get an error Property 'cronofy-scheduler-button' does not exist on type 'JSX.IntrinsicElements'.
when using the button component.
You can save the following as cronofy-scheduler-button.d.ts
to add the required types for the button element to your project:
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare interface CronofySchedulerButton extends HTMLElement {
["embed-token"]: string,
["correlation-id"]: string,
["recipient-email"]: string,
["recipient-name"]: string,
["recipient-organization-name"]: string,
["event-summary"]: string,
["event-duration-minutes"]: string
}
declare global {
namespace JSX {
interface IntrinsicElements {
['cronofy-scheduler-button']: DetailedHTMLProps<HTMLAttributes<CronofySchedulerButton>, HTMLElement>;
}
}
}