Skip to main content
The standard way to invoke RevenueHero is hero.schedule('#form-id'), which listens for your form’s submit event and opens the scheduler automatically. But that doesn’t always fit. Maybe you’re running server-side validation before showing the calendar. Maybe your form lives inside an iframe. Maybe you’re building in React and there’s no traditional form submit event to listen to. This article covers every way to invoke the RevenueHero scheduler on your website. For the step-by-step router setup that generates your script snippet, see Create Inbound Router.
BEFORE YOU BEGIN
  1. Create an Inbound Router and note your Router ID
  2. Add the RevenueHero source script to the <head> of your page:
<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>

Methods

The RevenueHero script exposes three methods. The first, hero.schedule(), is the standard method documented in every web forms integration guide. The other two, hero.submit() and hero.dialog.open(), give you programmatic control over the entire flow.

hero.schedule(formSelector)

Binds to a form’s native submit event. When the form is submitted, RevenueHero intercepts the data, runs routing rules, and opens the scheduler automatically.
const hero = new RevenueHero({ routerId: '123' });
hero.schedule('#demo-form'); // CSS selector for your form element
When to use: Your form uses a standard HTML submit and you don’t need to intercept the submission before the scheduler appears. Limitation: If you call e.preventDefault() on the form’s submit event before RevenueHero can listen to it, hero.schedule() will never fire. Use hero.submit() instead.

hero.submit(formData)

Sends form field data directly to RevenueHero for routing and qualification. Returns a Promise that resolves with session data you can use to open the scheduler.
const hero = new RevenueHero({ routerId: '123' });

hero.submit({
  email: 'jane@acme.com',
  firstname: 'Jane',
  lastname: 'Doe',
  company: 'Acme Inc'
}).then(function(sessionData) {
  hero.dialog.open(sessionData);
});
When to use: You need to control what happens between form submission and the scheduler appearing. Custom validation, async API calls, multi-step forms, SPA frameworks, or any flow where hero.schedule() doesn’t work.
Pass a plain object, not a FormData instance. If you’re collecting data from a form element, convert it first:
const formElement = document.getElementById('demo-form');
const formData = Object.fromEntries(new FormData(formElement).entries());
hero.submit(formData); // plain object like { email: '...', name: '...' }
Passing a raw FormData object will silently fail.

hero.dialog.open(sessionData)

Opens the scheduler popup programmatically. Takes the session data returned from hero.submit().
hero.submit(formData).then(function(sessionData) {
  hero.dialog.open(sessionData);
});
When to use: Always used together with hero.submit(). This is what actually makes the calendar visible to your prospect.

Constructor options

The RevenueHero constructor accepts a configuration object:
ParameterTypeRequiredDescription
routerIdstringYesYour Router ID from the Inbound Router setup
formTypestringNoSet to 'pardot' or 'jotform' for those form providers
showLoaderbooleanNoShows a loading spinner while routing rules are evaluated
const hero = new RevenueHero({
  routerId: '963',
  showLoader: true
});
Your Router ID is displayed in the Widget Installation step when you create or edit an Inbound Router.

Custom validation before scheduling

The most common reason to use hero.submit() is when you need to validate form data before showing the scheduler. Run your validation, and only trigger RevenueHero if it passes.
<form id="bookademoform">
  <input name="email" type="email" required />
  <input name="firstName" type="text" required />
  <input name="company" type="text" />
  <button type="submit">Request Demo</button>
</form>

<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>
<script type="text/javascript">
  const form = document.getElementById('bookademoform');

  form.addEventListener('submit', async function(e) {
    e.preventDefault();

    const data = Object.fromEntries(new FormData(form).entries());

    // Your custom validation (API call, domain check, etc.)
    const response = await fetch('https://your-api.com/validate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: data.email })
    });
    const result = await response.json();

    if (result.status === 'valid') {
      // Only show the scheduler for valid leads
      const hero = new RevenueHero({ routerId: '963', showLoader: true });
      hero.submit(data).then(function(sessionData) {
        hero.dialog.open(sessionData);
      });
    } else {
      // Show an error message
      document.getElementById('error').textContent = 'Please use a work email.';
    }
  });
</script>
This pattern is useful for running checks like email validation services, domain blocklists, or CRM lookups before consuming a RevenueHero routing. If the check fails, the prospect never sees the scheduler and no routing log entry is created.

Webflow forms (AJAX intercept)

Webflow forms submit via AJAX by default. If the standard hero.schedule() doesn’t trigger the scheduler on your Webflow site, use this jQuery-based intercept pattern:
<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>
<script type="text/javascript">
  jQuery(document).ajaxComplete(function(event, xhr, settings) {
    if (settings.url.includes('https://webflow.com/api/v1/form/')) {
      var formElement = document.querySelector('#wf-form-Email-Form');
      var isSuccessful = xhr.status === 200;
      var isTargetForm = settings.data.includes(formElement.dataset['wfElementId']);

      if (isTargetForm && isSuccessful) {
        var submitData = Object.fromEntries(new FormData(formElement).entries());
        var hero = new RevenueHero({ routerId: '123' });
        hero.submit(submitData).then(function(sessionData) {
          hero.dialog.open(sessionData);
        });
      }
    }
  });
</script>
Replace #wf-form-Email-Form with your Webflow form’s CSS ID and '123' with your Router ID.
This script must be placed in the Before </body> tag section in Webflow, not the <head>. The form element needs to exist in the DOM before the script runs.
For the standard Webflow installation that works without this intercept, see Webflow Forms.

React and Next.js

In single-page applications, the RevenueHero script needs to load dynamically since there’s no traditional page load.

React

import { useEffect, useRef } from 'react';

function DemoForm() {
  const formRef = useRef(null);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://app.revenuehero.io/scheduler.min.js';
    script.onload = () => {
      // Script loaded, RevenueHero is now available on window.RevenueHero
    };
    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(formRef.current).entries());

    const hero = new window.RevenueHero({ routerId: '123', showLoader: true });
    const sessionData = await hero.submit(data);
    hero.dialog.open(sessionData);
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="firstname" type="text" required />
      <button type="submit">Book a Demo</button>
    </form>
  );
}

Next.js

Use the Next.js Script component to load the RevenueHero script:
import Script from 'next/script';

export default function DemoPage() {
  const handleSubmit = async (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.target).entries());

    const hero = new window.RevenueHero({ routerId: '123', showLoader: true });
    const sessionData = await hero.submit(data);
    hero.dialog.open(sessionData);
  };

  return (
    <>
      <Script
        src="https://app.revenuehero.io/scheduler.min.js"
        strategy="afterInteractive"
      />
      <form onSubmit={handleSubmit}>
        <input name="email" type="email" required />
        <input name="firstname" type="text" required />
        <button type="submit">Book a Demo</button>
      </form>
    </>
  );
}

Gatsby

Gatsby uses the <Script> component from gatsby-script:
import { Script } from 'gatsby';

function DemoForm() {
  const handleSubmit = async (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.target).entries());

    const hero = new window.RevenueHero({ routerId: '123' });
    const sessionData = await hero.submit(data);
    hero.dialog.open(sessionData);
  };

  return (
    <>
      <Script src="https://app.revenuehero.io/scheduler.min.js" />
      <form onSubmit={handleSubmit}>
        <input name="email" type="email" required />
        <input name="firstname" type="text" required />
        <button type="submit">Book a Demo</button>
      </form>
    </>
  );
}

Google Tag Manager

You can load the RevenueHero script through GTM instead of adding it directly to your page HTML.

Loading the script via GTM

  1. In Google Tag Manager, create a new Custom HTML tag
  2. Add the script:
    <script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>
    <script type="text/javascript">
      window.hero = new RevenueHero({ routerId: '123' });
      hero.schedule('#demo-form');
    </script>
    
  3. Set the trigger to DOM Ready on the pages where your form exists
  4. Publish the container
Do not use the “All Pages” trigger with a Page View firing option. The script needs the form element to be present in the DOM when it executes. Use DOM Ready or Window Loaded as the trigger.

Tracking conversions with GTM

Use RevenueHero’s JavaScript Events to push conversion data into the GTM data layer:
<script type="text/javascript">
  window.addEventListener('message', function(ev) {
    if (ev.data.type === 'PAGE_LOADED') {
      // Fires when the scheduler is shown (lead qualified)
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({ event: 'rh_qualified' });
    }

    if (ev.data.type === 'MEETING_BOOKED') {
      // Fires when a meeting is booked
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'rh_meeting_booked',
        booker_email: ev.data.meeting.attributes.booker_email,
        meeting_time: ev.data.meeting.attributes.meeting_time
      });
    }
  });
</script>
The PAGE_LOADED event is particularly useful for Google Ads conversion tracking. It fires when the scheduler with booking slots is displayed, which means the lead passed your routing qualification. Use this as a “Qualified Lead” conversion action.

Global script across multiple pages

If you use the same form structure across many pages (e.g., the same HubSpot form on 400+ landing pages), you can install one script at the template level. Add the RevenueHero script to your site’s global template or theme footer. The script will look for the specified form selector on every page. If the form isn’t present on a particular page, the script does nothing.
<!-- Add to your site-wide template, theme footer, or CMS global code -->
<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>
<script type="text/javascript">
  window.hero = new RevenueHero({ routerId: '123' });
  hero.schedule('#demo-form');
</script>
If you have multiple forms mapped to different routers, use a single scheduler.min.js include in the <head> and separate hero.schedule() calls in the <body> of each page.

Re-trigger the scheduler (booking incomplete page)

If a prospect fills your form but doesn’t book a meeting, you can create a “Booking Incomplete” page that lets them try again. Store the form data in localStorage after the initial submission, then use hero.submit() on the retry page.

On your original form page

form.addEventListener('submit', function(e) {
  // Save the form data for the retry page
  var data = Object.fromEntries(new FormData(form).entries());
  localStorage.setItem('rhdata', JSON.stringify(data));
  localStorage.setItem('rhid', '963'); // your Router ID
});

On the “booking incomplete” page

<button id="book-now">Book a Time</button>

<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>
<script type="text/javascript">
  document.getElementById('book-now').addEventListener('click', function() {
    var savedData = localStorage.getItem('rhdata');
    var routerId = localStorage.getItem('rhid');

    if (savedData && routerId) {
      var hero = new RevenueHero({ routerId: routerId, showLoader: true });
      hero.submit(JSON.parse(savedData)).then(function(sessionData) {
        hero.dialog.open(sessionData);
      });
    }
  });
</script>

Troubleshooting

Scheduler doesn’t appear after form submit

Check the form selector. The CSS selector passed to hero.schedule() must match your form element exactly. Open your browser’s developer console and run document.querySelector('#your-form-id') to verify the form element exists. Check for e.preventDefault(). If your JavaScript calls e.preventDefault() on the form’s submit event before RevenueHero’s listener fires, the scheduler won’t trigger. Switch to hero.submit() + hero.dialog.open(). Check the browser console. Look for errors from scheduler.min.js. A 500 error from the RevenueHero API usually means a field mapping mismatch. Verify that the field names in your form match the mappings in your Inbound Router.

Script not loading

Don’t defer the script. Adding defer or async attributes to the scheduler.min.js script tag can cause it to load after your form’s submit handler runs. Load it synchronously in the <head> section.
<!-- Correct -->
<script type="text/javascript" src="https://app.revenuehero.io/scheduler.min.js"></script>

<!-- Avoid -->
<script defer src="https://app.revenuehero.io/scheduler.min.js"></script>
WordPress / WP Rocket users: WP Rocket and similar optimization plugins may automatically defer or lazy-load third-party scripts. Exclude scheduler.min.js from defer, delay, and minification settings.

Form inside an iframe

If your form renders inside an iframe (common with Pardot embedded forms), the parent page’s RevenueHero script cannot access the form’s submit event due to cross-origin restrictions. Solutions:
  1. Use the Pardot integration which handles this natively with formType: 'pardot'
  2. Add the RevenueHero script inside the iframe’s HTML (in the form’s “Below Form” or “Thank You” content area)
  3. If neither works, contact support. The team can build a custom script for your form handler setup.

PHP or CMS wrapping the script in HTML tags

Some CMS platforms (WordPress visual editors, PHP templates) may wrap your <script> tag in <p> or <div> tags, which breaks execution. Use your CMS’s raw HTML or code block to prevent this.
Your advanced script installation is configured. Once you’ve added the script to your page, submit a test form entry and check your Routing Logs to confirm submissions are flowing through. 🎉🎉🎉

JavaScript Events

Listen for MEETING_BOOKED, PAGE_LOADED, and other events from the scheduler widget.

Create Inbound Router

Set up routing rules and get your Router ID.

Mapping Your Form

Map your form fields so RevenueHero can use them in routing conditions.

Custom HTML Forms

Standard script installation for custom HTML forms.