Forms SDK
Last updated: July 29, 2026
The Default Forms SDK makes it easy to connect your hosted forms to Default’s platform. It streamlines form submissions, event management, and scheduler integration to enhance your workflows.
Forms SDK is used by customers like Hex to fully customize the form experience.
Features:
Form Submission Management. Effortlessly send form data to Default.
Event Emission. Hook into events like submission success or errors.
Scheduler Integration. Allow users to book meetings directly from your form.
Implementation
1. Create a form
Go to Default > Forms > Webforms and create a new form.
Pick Default SDK.
2. Include the forms SDK web script
Add the SDK to your webpage:
const script = document.createElement('script');
script.src = "https://import-cdn.default.com/sdk.js";
script.async = true; // Optional: Load asynchronously
document.head.appendChild(script);Access the SDK:
const sdk = window.DefaultSDK;3. Available methods
helloworld() : Logs a welcome message to verify that the SDK is loaded correctly.
sdk.helloWorld();
// Logs: "Hello World! Welcome to Default.com"submit() : Sends form responses to Default and manages your event callbacks based on workflow steps.
sdk.submit(
submission: {
form_id: number;
team_id: number;
responses: Record<string, string | string[] | number | boolean>;
questions: Array<{
id: string;
name: string;
type: string;
options?: Array<string | number>;
}>;
},
callbacks?: {
onSuccess?: (data) => void;
onError?: (error: Error) => void;
onSchedulerDisplayed?: (data) => void;
onSchedulerClosed?: (data: { redirectUrl?: string }) => void;
onMeetingBooked?: (data: { payload }) => void;
}
): Promise<void>;reportSchedulerNotBooked() : Tells Default that the lead closed the scheduler without booking, so the Meeting not booked path in your workflow runs straight away instead of waiting for the scheduler to time out. You only need this if you have replaced the scheduler’s own close button with your own. See Reporting when a lead closes your scheduler.
DefaultSDK.reportSchedulerNotBooked();
// Returns true if the report was sent, false if there was nothing to report4. Submission and Callbacks
Submission
Required javascript object containing form data and metadata:
form_id(number, required): Unique form identifierteam_id(number, required): Team identifierresponses(object, required): Question ID to response mappingquestions(array, required): Question metadata array
Question attributes
For each object in the question array, the following key value pairs are required:
id(required): The unique identifier for the question.name(required): The human-readable name of the question.type(required): The type of question input:email: The lead's email address.input: Short Text input fields.textarea: Long Text input fields.
selectorradio: Single Select input where leads can only select one option.
checkbox: Whether a checkbox was selected or not.select-multiple: Multi Select input where leads can select multiple options.
tel: Phone number.
options(optional): For multiple-choice questions, an array of valid options.lead_attribute(optional): The lead attribute in Default you want to map the input field to:first_name: The lead's first name.
last_name: The lead's last name.
phone: The lead's phone number.
role: The lead's role at their company.
title: The lead's job title.
city: The city that the lead is located in.
location: The address of the lead.
company: The company's name.
industry_group: The company's industry.
head_count: The company's headcount.
Example submission:
const submission = {
form_id: FORM_ID,
team_id: TEAM_ID,
responses: {
email: "test@test.com",
industry: "automotive",
},
questions: [
{
id: "email",
name: "Email",
type: "email",
options: [],
},
{
id: "industry",
name: "Industry",
type: "input",
options: ["automotive", "computers", "software"],
},
],
};Callbacks (Optional)
The following event handlers are available through the lifecycle of a form submission:
const callbacks = {
onSuccess: (data) => {
console.log("Submission successful!", data);
},
onError: (error) => {
console.error("Submission failed:", error.message);
},
onSchedulerDisplayed: (data) => {
console.log("Scheduler displayed:", data);
},
onSchedulerClosed: (data) => {
console.log("Scheduler closed, optional redirect: " + data.redirect);
},
onMeetingBooked: (data) => {
console.log("Meeting booked successfully!", data.payload);
},
onRedirect: (data) => {
console.info("Redirect URL received:", data.redirectUrl);
window.open(data.redirectUrl, "_blank");
}
};Scheduler display control
Use this when you need to have control over where and when a scheduler is displayed.
// Disable automatic scheduler display and manually control when to redirect
DefaultSDK.submit(submission, {
autoSchedulerDisplay: false, // Prevents automatic scheduler display
onSuccess: (response) => {
console.log("Submission successful:", response);
// Display scheduler in custom iframe
const iframe = document.getElementById("scheduler");
if (iframe) {
iframe.style.display = "block";
iframe.src = response.body.stepDetails.url;
}
}
});When autoSchedulerDisplay is set to false, the scheduler won't automatically appear after form submission. Instead, you receive the scheduler URL in the onSuccess callback, giving you full control over when and where to display it. All scheduler-related callbacks (onSchedulerDisplay, onSchedulerClosed, onMeetingBooked) continue to fire normally, allowing you to maintain full visibility into the scheduler's lifecycle while customizing its presentation.
Embedding the scheduler on your own thank-you page? See How to embed the scheduler with the Forms SDK redirect flow for the workflow setting, the copy-pasteable thank-you-page script, and tips for handling the booking, confirmation, reschedule, and cancel views.
Reporting when a lead closes your scheduler
If your workflow has a Meeting not booked path, Default needs to know when a lead has given up on booking so it can follow that path — for example, to route the lead for manual review or send a follow-up email.
Default learns this in one of two ways:
- The lead closes the scheduler. When the lead uses the scheduler’s own close button, Default is notified immediately and the Meeting not booked path runs within seconds.
- The scheduler times out. This runs on Default’s servers, so it happens even if the lead closes the tab or never comes back. It fires after the delay configured on your scheduler step, plus a short grace period — around six minutes with the default five-minute setting.
If you embed the scheduler inline and hide its close button with hide_close=true so that you can use your own close button or overlay, the first of those two signals no longer happens. Your close button belongs to your page, not to the scheduler, so Default cannot see it. Nothing is lost — the timeout still runs and is always the authoritative signal — but the Meeting not booked path will not run until that timer expires.
Call reportSchedulerNotBooked() from your own close handler to close that gap:
// Your own close button, next to an inline scheduler embedded with hide_close=true
document.getElementById("closeScheduler").addEventListener("click", () => {
// Hide the scheduler however you like
document.getElementById("scheduler").style.display = "none";
// Tell Default the lead closed it without booking
DefaultSDK.reportSchedulerNotBooked();
});The method takes no arguments — Default already knows which submission the scheduler belongs to from the response you received in onSuccess. It returns true if a report was sent, and false if there was nothing to report.
When to use it
- You embed the scheduler inline with
hide_close=trueand provide your own close button, overlay dismissal, or “skip for now” link, and your workflow has a Meeting not booked path that should run promptly.
When not to use it
- You are happy for the Meeting not booked path to run on the timeout instead. No code needed.
- You want the lead to be able to close and reopen the scheduler without triggering the Meeting not booked path. In that case simply hide your scheduler and do not call this method — hiding it on its own never reports anything.
- You are using the scheduler’s own close button, which already notifies Default.
Good to know
- It is safe to call more than once, and safe to call after a meeting has already been booked. If the lead did book, the method does nothing and returns
false, so a successful booking is never overwritten. The Meeting not booked path also never runs twice. - Call it when the lead actually dismisses the scheduler, rather than when your page unloads. Leads who simply close the tab are already covered by the timeout.
- If you show more than one scheduler on the same page, only the most recent one you displayed is reported.
- Available on both
DefaultSDKand the__default__script loader, as__default__.reportSchedulerNotBooked().
Redirect control
Use this when you need to have control over where and when to redirect your users.
// Disable automatic workflow redirect and manually control when/where it appears
DefaultSDK.submit(submission, {
autoRedirect: false, // Prevents automatic scheduler redirect
onRedirect: (data) => {
console.info("Redirect URL received:", data.redirectUrl);
window.open(data.redirectUrl, "_blank");
}
});5. Create a test submission
Create a test submission by submitting a form entry to ensure your setup is working properly. Then, navigate to the Check the Connection step in Default to verify the integration.Complete the Configuration by:
Select the email field to identify the lead.
Map your form fields to the corresponding Default attributes.
Assign a unique name to the form for easy identification.
This process ensures your form is correctly integrated and ready for future submissions.
Forms SDK example
Below is an example snippet that submits a form response using forms SDK.
const script = document.createElement('script');
script.src = "https://import-cdn.default.com/sdk.js";
script.async = true;
script.onload = function() {
console.log("SDK script loaded, attempting to use DefaultSDK...");
const submission = {
form_id: 867190,
team_id: 7,
responses: {
email: "luke@tatooine.galaxy",
rebel_name: "Luke Skywalker",
home_planet: "Tatooine",
spacecraft_type: "X-wing",
additional_spacecraft_type: ["B-wing", "Y-wing"],
combat_experience: "Yes",
force_sensitive: "Yes",
preferred_division: "starfighter_corps",
mission_availability: "immediate",
droid_companion: "R2-D2",
imperial_history: "No"
},
questions: [
{
id: "email",
name: "Contact Email",
type: "email",
options: []
},
{
id: "rebel_name",
name: "Full Name",
type: "input",
options: []
},
{
id: "home_planet",
name: "Home Planet",
type: "input",
options: []
},
{
id: "spacecraft_type",
name: "Primary Spacecraft",
type: "select",
options: ["X-wing", "Y-wing", "A-wing", "B-wing", "Other"]
},
{
id: "additional_spacecraft_types",
name: "Additional Spacecrafts",
type: "select-multiple",
options: ["X-wing", "Y-wing", "A-wing", "B-wing", "Other"]
},
{
id: "combat_experience",
name: "Do you have combat experience?",
type: "select",
options: ["Yes", "No"]
},
{
id: "force_sensitive",
name: "Are you Force-sensitive?",
type: "select",
options: ["Yes", "No"]
},
{
id: "preferred_division",
name: "Preferred Division",
type: "select",
options: ["starfighter_corps", "ground_forces", "intelligence", "support_services"]
},
{
id: "mission_availability",
name: "When can you start missions?",
type: "select",
options: ["immediate", "within_week", "within_month", "need_training"]
},
{
id: "droid_companion",
name: "Droid Companion (if any)",
type: "input",
options: []
},
{
id: "imperial_history",
name: "Have you ever served the Empire?",
type: "select",
options: ["Yes", "No"]
}
]
};
const callbacks = {
onSuccess: (data) => console.log("Welcome to the Rebellion!", data),
onError: (error) => console.error("Submission failed:", error.message),
onSchedulerDisplayed: (data) => console.log("Scheduler displayed:", data),
onSchedulerClosed: (data) => console.log("Scheduler closed:", data),
onMeetingBooked: (data) => console.log("Meeting booked:", data.payload)
};
try {
console.log("Attempting submission with DefaultSDK...");
// Using the DefaultSDK that we found in the window properties
window.DefaultSDK.submit(submission, callbacks);
} catch (e) {
console.error("Error during submission:", e);
console.log("DefaultSDK object:", window.DefaultSDK);
}
};
script.onerror = function() {
console.error("Failed to load the Forms SDK");
};
document.head.appendChild(script);Content Security Policy
If adding to a Content Security Policy (CSP), add *.default.com to your existing lists.For a more specific list of subdomains, you can add the following:
HTMLscript-src 'self' https://import-cdn.default.com;
style-src 'self' https://import-cdn.default.com;
connect-src 'self' https://nucleus.default.com;Error handling
Client-Side validation (SDK errors emitted via callback)
Errors will be triggered if:
Any of the following fields:
form_id,team_id,responses,questions, oremailare missingresponsesis not an objectquestionsis not an arrayA
responsekey does not match or correspond to an entry in the questions array
Server-Side Errors (400 Bad Request)
The server will return a 400 error if:
submissionis missingform_idorteam_idis missingresponsesorquestionsare missingemailis missingThe
questions[]array does not match the expected formatNo valid responses are found
Server-Side Errors (500 Internal Server Error)
The endpoint is experiencing an outage
Response structure
Success
JSON{"body": {
"stepDetails": {
"inline": true, // If true, display a scheduler modal; if false, redirect to a URL
"url": "https://example.com/scheduler-or-redirect-url"
}},"success": true}
Error
JSON{"error": "Error message explaining why the response was not processed.","success": false}