Skip to content
Lapis
Esc
navigateopen⌘Jpreview
On this page

Lead gen & bookings

Send lead_created and appointment_scheduled for forms, demos, consultations, and bookings.

For businesses whose conversion is a person raising their hand: form fills, demo requests, consultations, and appointments. The mechanics are simpler than ecommerce. The hard part is send timing and lead quality.

Event priority

Priority Event Fires when Why it matters
1 lead_created A qualified form submission lands in your system The primary conversion for most lead-gen campaigns.
2 appointment_scheduled A demo or consultation is actually booked A stronger signal than a form fill. Use it as your primary event if booking is your real goal.
3 custom (for example, lead_qualified) Your team or CRM marks the lead as real An optional quality signal for later analysis.

If visitors book directly (Calendly-style), skip lead_created and make appointment_scheduled your only event. One business conversion means one revenue-bearing event.

lead_created

Send it server-side, from your form handler or CRM webhook. Do not send it from the form’s JavaScript onSubmit, which fires even when your backend rejects the submission and double-fires on retries.

{
  "event_id": "lead_created:lead_20d84a",
  "event_type": "lead_created",
  "lapis_click_id": "<from the hidden form field>",
  "properties": {
    "form": "contact_sales",
    "source_page": "/pricing"
  }
}

Event ID convention: use lead_created:<crm_lead_id> when your CRM assigns IDs synchronously. Otherwise, use a hash of email plus form plus day (lead_created:contact_sales:9c41ab) so the same person double-submitting the same form does not count twice.

Getting the click ID into the form

Add a hidden field and populate it from the cookie:

<input type="hidden" name="lapis_click_id" id="lapis-click-id-field" />
<script>
(function () {
  var cookie = document.cookie.split("; ").find(function (entry) {
    return entry.indexOf("lapis_sid=") === 0;
  });
  if (!cookie) return;
  try {
    var session = JSON.parse(
      decodeURIComponent(cookie.split("=").slice(1).join("="))
    );
    if (session.sid) {
      document.getElementById("lapis-click-id-field").value = session.sid;
    }
  } catch (e) {}
})();
</script>

Any form tool that supports hidden fields with dynamic values works with this pattern (HubSpot, Typeform hidden fields, Webflow forms with custom code). The value just has to arrive wherever your webhook fires.

appointment_scheduled

Fire this from your scheduling tool’s webhook (Calendly invitee.created, Cal.com BOOKING_CREATED, or your own booking system):

{
  "event_id": "appointment_scheduled:cal_evt_77ab2",
  "event_type": "appointment_scheduled",
  "lapis_click_id": "<from booking metadata>",
  "value": 150.00,
  "currency": "USD",
  "properties": { "meeting_type": "demo_30min" }
}

For embedded schedulers, pass the click ID through the embed’s prefill or UTM options (Calendly uses utm_content; Cal.com uses metadata) so it comes back in the webhook payload.

Cancellations: as with refunds in ecommerce, there is no reversal event. A booking that later cancels still counted, so track show rates in your own reporting.

Valuing leads

lead_created has no natural revenue, but ad optimization works much better with values attached. Here are three strategies, in increasing order of effort:

Strategy How When to use
Flat value Every lead gets value: 50 (your average lead value) Default. Simple, and it unlocks value-based comparison across ads.
Tiered by intent contact_sales form gets 150, newsletter gets 5 Multiple forms with clearly different intent.
Backfilled quality Send lead_created unvalued, then later send a custom lead_qualified event with a value once the CRM qualifies it Long sales cycles with good CRM hygiene. Remember the 30-day attribution window.

What good looks like

Read the funnel left to right. Each number is how many reached that stage.

sessions → page_views → lead_created → appointment_scheduled
   600   →   1,400    →      38      →         12

Leads arriving with no lapis_click_id at all usually means the hidden-field script runs before the cookie exists, or the form sits on a page without the pixel. Confirm the pixel snippet loads on every page where a form appears, not just the ad landing page.

Was this page helpful?