API Reference

Webhooks

Payload schemas, signature verification, and delivery behavior for webhooks SuperFunnel sends to your endpoint.

Webhooks are the one part of the platform where SuperFunnel calls you. When a visitor submits a form, fills one out partially, or interacts with a page, SuperFunnel sends an HTTP request to an endpoint you own.

This page documents what arrives at that endpoint, and how to confirm the request genuinely came from SuperFunnel.

Setting a webhook up

Creating a webhook, choosing its event types, and configuring method, content type, and delivery filters all happen in the dashboard. See the Webhooks guide for that walkthrough.

Defaults: a POST with a JSON body. The method and content type are both configurable per webhook, so the schemas below describe the default JSON encoding.

Webhook types

Each webhook subscribes to exactly one type, chosen when you create it. The type field on the root of every payload tells you which one fired.

TypeTrigger
new_leadA visitor completes and submits a form.
new_partial_leadSent incrementally as a visitor fills out a form, before they submit.
new_eventA visitor interaction on the page, filtered by the event types you pick.

For new_event, you choose which interactions to receive. The available event types are view, conversion, scroll, button-click, form-submission, ping, scheduled-event, user-error, popup-view, popup-click, and popup-close (or all of them).

Authentication

Secure your webhook with one of two methods, selected when you create it.

You define a static key in the SuperFunnel UI. It is sent in the HTTP Authorization header on every request, exactly as you entered it:

Authorization: <your-custom-api-key>

Reject any request whose header does not match your key.

Method B: Public/private key

SuperFunnel generates an RSA key pair and signs every request, placing a token in the Authorization header. Download the public key from the integration's settings and use it to verify that token before trusting the payload. The token is a JWT signed with RS256, carries the claim iss: "superfunnel.ai", and expires one hour after it is issued.

import jwt from 'jsonwebtoken'

// publicKey: the PEM you downloaded from the webhook's settings.
// token: the raw value of the Authorization header (no "Bearer " prefix).
function verifyRequest(token: string, publicKey: string) {
  return jwt.verify(token, publicKey, {
    algorithms: ['RS256'],
    issuer: 'superfunnel.ai',
  }) // throws if the signature is invalid or the token has expired
}

Delivery behavior

  • Retry policy — if your endpoint does not return a 200 OK, SuperFunnel retries the delivery up to 3 times with exponential backoff.
  • Timeout — respond quickly so a slow handler doesn't trigger a retry. Acknowledge with a 200 first and do the real work asynchronously.
  • At-least-once delivery — a retried delivery repeats a payload your endpoint may have already processed. Deduplicate on data.id, which is stable across retries of the same event.
  • Object mapping — by default you receive the full payloads documented below. If object mapping rules are defined on the webhook, the default payload is discarded and your endpoint receives only the structure defined there.
  • Per-field encoding — mapped fields carry an encoding, set per row and defaulting to none. A field may arrive Base64, URL-safe Base64, or as a JSON string, in which case your handler decodes it before use. Encoding is a property of the mapping, not the payload, so nothing in the request identifies which fields were encoded. Read the webhook's configuration to know what to expect.

Partial leads fire repeatedly

A new_partial_lead webhook sends a payload at every step of the form, not just on completion. Either configure a delivery filter so SuperFunnel only sends payloads containing a key field, or gate your handler on that field yourself, so half-finished records don't pile up downstream.

Payload format

Every default delivery has a JSON body whose root contains a type field and a data object.

New lead

new_lead fires when a visitor completes and submits a form. Form answers live under data.form, keyed by field with a value and label. createdAt is a Firestore timestamp (_seconds / _nanoseconds).

{
  "type": "new_lead",
  "data": {
    "accountId": "0RtsITKvLklzoTXqSZaO",
    "campaignId": "UubgLfCPScWvTXN3hSkI",
    "variantId": "bA3YhT2gLXzOCyo7qqeA",
    "websiteId": "CuRMjNPCe4WCVOOITqru",
    "formId": "JYgrG3oeO8GLgKlBPas9",
    "formName": "Lead capture form",
    "submissionId": "TkDAGQktTGHf",
    "form": {
      "name": { "value": "Jane Doe", "label": "Name" },
      "email": { "value": "jane@example.com", "label": "Email" },
      "phone": { "value": "+15555550123", "label": "Phone" },
      "message": { "value": "", "label": "Message" }
    },
    "url": "https://yourbrand.superfunnel.page/landing?utm=spring_sale",
    "queryParams": { "utm": "spring_sale" },
    "ip": "203.0.113.42",
    "userHash": 6240556724501698,
    "sessionHash": 3613007597330076,
    "visitHash": 8079222503494134,
    "id": "6jXpAC82AANQ0GLSq8d5",
    "createdAt": { "_seconds": 1767621070, "_nanoseconds": 212000000 }
  }
}

Partial lead

new_partial_lead is sent repeatedly as the visitor fills out the form, so most payloads are incomplete. It adds currentStep and lastUpdatedAt, and its id is {sessionHash}_{submissionId} rather than a standalone document ID. Upsert on that id to keep one row per in-progress submission.

{
  "type": "new_partial_lead",
  "data": {
    "accountId": "0RtsITKvLklzoTXqSZaO",
    "campaignId": "UubgLfCPScWvTXN3hSkI",
    "variantId": "bA3YhT2gLXzOCyo7qqeA",
    "websiteId": "CuRMjNPCe4WCVOOITqru",
    "formId": "1Ts6JMftPUTsOdOMTPWC",
    "formName": "Qualify for Your Debt Consolidation Loan",
    "submissionId": "dpke3fVx1gLP",
    "currentStep": 0,
    "form": {
      "name": { "value": "Jane Doe", "label": "Name" },
      "email": { "value": "jane@example.com", "label": "Email" }
    },
    "url": "https://yourbrand.superfunnel.page/landing?utm=spring_sale",
    "queryParams": { "utm": "spring_sale" },
    "userHash": 6240556724501698,
    "sessionHash": 3613007597330076,
    "visitHash": 5291661038735089,
    "id": "3613007597330076_dpke3fVx1gLP",
    "lastUpdatedAt": { "_seconds": 1767621954, "_nanoseconds": 474000000 },
    "createdAt": { "_seconds": 1767621954, "_nanoseconds": 474000000 }
  }
}

Page events

new_event is fired by visitor interactions. The event field names the interaction, and the rest carries full visitor context (device, location, referrer, and session hashes). A scroll payload is identical to a view but with "event": "scroll".

{
  "type": "new_event",
  "data": {
    "id": "59176294-00fd-42e2-8003-4afe85421adb",
    "event": "view",
    "createdOn": "2026-01-05T13:49:19.616Z",
    "createdOnUnix": 1767620959616,
    "accountId": "0RtsITKvLklzoTXqSZaO",
    "campaignId": "UubgLfCPScWvTXN3hSkI",
    "variantId": "bA3YhT2gLXzOCyo7qqeA",
    "websiteId": "CuRMjNPCe4WCVOOITqru",
    "path": "/landing",
    "search": "?utm=spring_sale",
    "title": "Home",
    "url": "https://yourbrand.superfunnel.page",
    "queryParams": { "utm": "spring_sale" },
    "ip": "203.0.113.42",
    "clientIp": "203.0.113.42",
    "device": "Desktop",
    "os": "Mac OS",
    "browser": "Chrome",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
    "origin": "https://yourbrand.superfunnel.page",
    "referrer": "https://app.superfunnel.ai/",
    "referrerDomain": "app.superfunnel.ai",
    "adNetworkProvider": "unknown",
    "country": "US",
    "region": "CA",
    "city": "San Francisco",
    "isBot": false,
    "hash": 6240556724501698,
    "sessionHash": 3613007597330076,
    "visitHash": 8079222503494134,
    "shortHash": "sA4v2N2Fk",
    "shortSessionHash": "gxX1xyTeY",
    "shortVisitHash": "B0bf3xjzE"
  }
}

A button-click event includes the same fields plus an extra object with DOM and element details:

"extra": {
  "rawHTML": true,
  "buttonId": "3c68e6bc",
  "buttonPath": "section#section-html-v1 > div > button.inline-flex",
  "sectionId": "99bbc91cf844d660",
  "sectionType": "html",
  "sectionIndex": 0,
  "buttonType": "form",
  "buttonLink": null
}

Session identifiers

sessionHash, visitHash, and userHash appear on every payload and are the same identifiers the conversions endpoint matches on. Storing them alongside a lead is what lets you report a backend conversion later and attribute it to the visit that produced it.

Where to go next