Form Webhooks
When a form is configured with a webhook URL (Form Settings → Webhook), every successful submission is delivered to that URL as a JSON POST from RocketLead’s backend. Use webhooks to land submissions in your CRM, kick off workflows, or trigger conversion tracking server-side.
Trigger
Section titled “Trigger”A webhook delivery is queued in the same atomic transaction as the submission. After the transaction commits, a worker picks it up and delivers. If queue dispatch fails, a sweeper retries — the delivery is durable.
The event is always form.submission for now. Future event types are reserved.
Transport
Section titled “Transport”- HTTP
POSTto the configuredwebhookUrl. Content-Type: application/json.- 10-second receiver timeout (
AbortController).
Payload
Section titled “Payload”{ "event": "form.submission", "formId": "<uuid>", "formName": "Probetraining 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": "user@example.com" }, "tableEntryId": "entry_...", "calendarBookingId": "booking_...", "fieldContext": { "field-loc-abc": { "label": "Standort", "type": "location", "name": "standort", "resolved": { "label": "Berlin Mitte" } }, "field-apt-xyz": { "label": "Kursinteresse", "type": "appointment-type", "name": "kursinteresse", "resolved": { "label": "Anfängerkurs" } }, "field-email": { "label": "E-Mail", "type": "email", "name": "email", "specialType": "email" } }, "resolvedData": { "standort": "Berlin Mitte", "kursinteresse": "Anfängerkurs", "email": "user@example.com" }}Top-level fields
Section titled “Top-level fields”| Field | Description |
|---|---|
event | Always "form.submission". |
formId | UUID of the form. |
formName | Editor-set form name at delivery time. |
submittedAt | ISO-8601 submission timestamp. Frozen at submission. |
data | Raw submitted values, keyed by field UUID. Bit-identical to the persisted submission — UUIDs preserved as-is for GDPR/audit. |
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 field or none was picked. |
fieldContext | Per-field metadata keyed by field UUID. See Field Context. |
resolvedData | Convenience flat projection keyed by stable field name. See Resolved Data. |
Field Context
Section titled “Field Context”fieldContext contains an entry for every field in the published config — including showIf-hidden fields and unanswered fields. Object.keys(fieldContext) may therefore be a superset of Object.keys(data). Entries without a submitted value omit the resolved block.
| Key | Description |
|---|---|
label | User-set form label, e.g. “Kursinteresse”. |
type | The field type enum (text, email, tel, select, checkbox, location, appointment-type, booking-calendar, captcha, privacy). See Field Types. |
name | Stable field name, when set. Optional. |
specialType | Destination-column key (email, firstName, etc.) when the field is mapped to a Lead-Pool column. |
resolved | Resolved human label for UUID-bearing fields. Omitted for primitives and for UUIDs that couldn’t be resolved (e.g., admin deleted the referenced entity post-submission). |
For location and appointment-type fields, resolved is { label: "..." }. For booking-calendar fields, resolved is the structured slot context:
"resolved": { "appointmentTypeLabel": "Anfängerkurs", "calendarLabel": "Berlin Kalender", "resourceLabel": "Trainer A", "startDateTime": "2026-05-01T10:00:00Z", "endDateTime": "2026-05-01T10:30:00Z"}Resolved Data
Section titled “Resolved Data”resolvedData is a flat object keyed by the stable field name (when set) or the field UUID (fallback), with the human-facing value pre-substituted:
- Primitive fields → the raw submitted value (same as
data[fieldId]). location/appointment-type→ the resolved label string. Falls back to the raw UUID when resolution failed.booking-calendar→ the structuredresolvedblock (same shape asfieldContext[...].resolved).
Fields with no submitted value AND no resolved block are omitted — showIf-hidden siblings drop out naturally.
Stability across retries
Section titled “Stability across retries”| Field | Frozen at submission | Rebuilt every delivery |
|---|---|---|
data | ✓ | |
submittedAt | ✓ | |
tableEntryId / calendarBookingId | ✓ | |
formName | ✓ | |
fieldContext (labels, resolved blocks) | ✓ | |
resolvedData | ✓ |
Retry policy
Section titled “Retry policy”Failed deliveries (any non-2xx response or fetch error) are retried with exponential backoff:
| Attempt | Backoff |
|---|---|
| 1 | (immediate) |
| 2 | 30s |
| 3 | 60s |
| 4 | 120s |
| 5 | 240s (capped at 300s) |
After 5 attempts the delivery is marked failed and won’t retry without admin intervention from the form’s Deliveries view. Response bodies are captured (truncated to 4096 chars) for debugging.
Signature verification
Section titled “Signature verification”A signed-webhook upgrade is on the roadmap.
Example handler
Section titled “Example handler”app.post('/webhook', (req, res) => { const { data, resolvedData, fieldContext, submissionId } = req.body;
// Idempotency — bail if we've already seen this submission if (await alreadyProcessed(submissionId)) { return res.status(200).end(); }
// Fast path: read pre-resolved labels keyed by stable name console.log(`Email: ${resolvedData.email}`); console.log(`Course interest: ${resolvedData.kursinteresse}`);
// Deep inspection: walk fieldContext + data for every field for (const [fieldId, value] of Object.entries(data)) { const ctx = fieldContext[fieldId]; console.log(`[${ctx.label}] (${ctx.type}) = ${value}`); }
res.status(200).end();});Related
Section titled “Related”- Public API → submit — the submission that triggers the webhook.
- Field Types — value shapes per type, as they appear in
data. - Events → ad-pixel integration — pair
submissionIdhere with thesubmit:successDOM event for cross-channel dedup. - Embedding → Stable Field Names — where
name(theresolvedDatakey) comes from.