Skip to main content

Form Widget: Events, Pixel Tracking & Webhooks

Capture form events for Meta Pixel, TikTok, Google Ads, GTM — and receive submissions via webhook

M
Written by Maxim Tan

The RocketLead form widget emits browser events and POSTs every submission as JSON to your webhook. Use either side for pixel tracking (Meta, TikTok, Google Ads), GTM integrations, or downstream automation.

Available events

Event

Description

Key fields

dataLayer

rl:form:view

Form loaded

formId, studioId

rl:form:step

Step navigation

formId, step

rl:form:field:change

Field value changed (every keystroke)

formId, fieldId, fieldName?

rl:form:field:blur

Field lost focus

formId, fieldId, fieldName?

rl:form:submit:start

Submission started

formId

rl:form:submit:success

Submission succeeded

formId, submissionId, formData, bookingSlot

rl:form:submit:error

Submission failed

formId, errorCode

rl:form:booking:slot

Booking slot selected

formId, fieldId, fieldName?, slotKey

rl:form:captcha:success

Captcha passed

formId, provider

rl:form:prefill

Fields prefilled on mount

formId, studioId, prefilledFieldIds

Note: rl:form:field:change and rl:form:field:blur are deliberately not pushed to the GTM dataLayer — they fire on every keystroke / focus-leave and would flood the container with per-field noise. Both still fire as DOM CustomEvents, so you can subscribe via document.addEventListener(...) when you need them.

Stable field names

Each form field can have a user-defined Name (in the editor → field settings). When set, it appears in two places:

  • As the HTML name="" attribute on the rendered input — for stable DOM selectors like input[name="email"] across every form on your domain.

  • As the fieldName property on field- and booking-events — usable directly in GTM triggers without walking the DOM.

Fields without a name set fall back to the internal field id. Consumers should treat fieldName as optional with a fallback to fieldId.

Listening to events

document.addEventListener('rl:form:submit:success', (e) => {   const { formId, submissionId, formData, bookingSlot } = e.detail;   // formData contains submitted values keyed by the field name   // e.g. { email: '[email protected]', firstName: 'Max', phone: '+491701234567' } });

Meta Pixel

document.addEventListener('rl:form:submit:success', (e) => {   const { formId, submissionId, formData } = e.detail;   fbq('track', 'Lead', {     content_name: formId,     external_id: submissionId, // for deduplication   });   fbq('init', 'YOUR_PIXEL_ID', {     em: formData.email,     ph: formData.phone,     fn: formData.firstName,   }); });

TikTok Pixel

document.addEventListener('rl:form:submit:success', (e) => {   const { formId, formData } = e.detail;   ttq.track('SubmitForm', {     content_id: formId,     email: formData.email,     phone_number: formData.phone,   }); });

Google Ads Enhanced Conversions

document.addEventListener('rl:form:submit:success', (e) => {   const { submissionId, formData } = e.detail;   gtag('event', 'conversion', {     send_to: 'AW-XXXXX/XXXXX',     transaction_id: submissionId,     user_data: {       email: formData.email,       phone_number: formData.phone,     }   }); });

Google Tag Manager (GTM)

Prerequisite: window.dataLayer must exist. The widget never creates the dataLayer itself. If the GTM snippet is on the page, window.dataLayer = window.dataLayer || [] already exists and pushes work automatically. Without GTM or with a renamed dataLayer, pushes silently no-op.

If you need pushes but don't use GTM, initialise the dataLayer yourself before the widget loads:

<script>window.dataLayer = window.dataLayer || [];</script> <script src="https://cdn.rocketlead.io/static/forms/widget.js" defer></script>

GTM setup

  1. Create a Custom Event trigger for rl:form:submit:success

  2. Create Data Layer Variables for formData.email, formData.phone etc.

  3. Use these variables in your Meta / TikTok / Google Ads tags

Webhook payload

When you configure a Webhook URL in the form settings, RocketLead POSTs a JSON payload to that URL on every submission. Retry policy: up to 4 attempts with exponential backoff (30s → 60s → 120s → 300s).

Payload shape

{   "event": "form.submission",   "formId": "6bb76cc2-caee-4154-ad60-f4da93bb24f7",   "formName": "Trial Lesson Berlin",   "submittedAt": "2026-04-22T12:00:00Z",   "data": {     "field-loc-abc": "3a1b2c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d",     "field-apt-xyz": "7d9e8f2c-4b1a-4c3d-8e5f-6a7b8c9d0e1f",     "field-email": "[email protected]"   },   "tableEntryId": "17d2cdca-ddb4-445d-9713-81c1a1bb90e2",   "calendarBookingId": null,   "fieldContext": {     "field-loc-abc": {       "label": "Studio",       "type": "location",       "name": "studio",       "resolved": { "label": "Berlin Mitte" }     },     "field-apt-xyz": {       "label": "Course interest",       "type": "appointment-type",       "name": "courseInterest",       "resolved": { "label": "Beginner course" }     },     "field-email": {       "label": "Email",       "type": "email",       "name": "email"     }   },   "resolvedData": {     "studio": "Berlin Mitte",     "courseInterest": "Beginner course",     "email": "[email protected]"   } }

Fields

Field

Description

data

Raw values keyed by field id. UUIDs (e.g. studio id, appointment-type id) are preserved — for audit / forensics.

tableEntryId

UUID of the created Lead-Pool entry. null for webhook-only forms.

calendarBookingId

UUID of the created booking. null if the form has no booking calendar or no slot was picked.

fieldContext

Per-field metadata (label, type, name, resolved labels for UUID fields). Contains an entry for every field in the published config — including unanswered and conditionally hidden fields.

resolvedData

Flat projection keyed by the stable field name (fallback: field id), with human-facing values pre-substituted. Convenient for Make.com / n8n / Zapier.

Example handler

app.post('/webhook', (req, res) => {   const { resolvedData, fieldContext } = req.body;  // Fast path: read pre-resolved values directly by field name   console.log(`Email: ${resolvedData.email}`);   console.log(`Course interest: ${resolvedData.courseInterest}`);   console.log(`Studio: ${resolvedData.studio}`);  res.status(200).end(); });

Note: data is frozen at submission time — UUIDs never change across retries. fieldContext and resolvedData are rebuilt on every delivery attempt, so a later delivery reflects admin renames of appointment types / calendars / resources.

Related articles

For more on prefilling fields from URL params, the embed code, persistence, or editor defaults — including the precedence order in which these sources override each other:

Debug mode

To see all events in real time during development, place a debug panel next to the form:

<div data-rocketlead-form data-form-id="your-form-id"></div> <div data-debug-form-id="your-form-id"></div>

The debug panel displays events with timestamps and full payloads as they fire.

Did this answer your question?