ROASFormDocs
Webhooks

The webhook

View as Markdown

The top-level structure shared by every delivery, and the shared object shapes inside data.

Every event shares one top-level structure. Only event and data vary. Every id in the payload is a stable UUID that is safe to store and reference. All timestamps are ISO 8601 in UTC and end in Z.

We announce every payload change in advance. New fields are additive and never change the webhookVersion; build your integration to ignore fields it does not recognize, so additions never affect it. A field is only removed or renamed with a new webhookVersion, announced ahead of time with a migration period.

Attributes

iduuid

Unique delivery id, stable across retries. Use it to deduplicate.

eventstring

The event type.

Possible values
form_submissionform_stepcompleted_leadpartial_leadbooked_call
webhookVersionstring

The payload version this webhook is pinned to. You control it from the webhook's settings.

createdAtUtctimestamp

When the event happened, for example the moment the form was submitted. If a delivery is retried, this keeps its original value, so it is not the time the HTTP request was sent.

agencyobject

The agency the subaccount belongs to.

+Show child attributes
iduuid
namestring
subaccountobject

The subaccount that owns the webhook and the form.

+Show child attributes
iduuid
namestring
formobject

The form the event happened on.

+Show child attributes
iduuid
namestring
urlstring
Link to the live form.
dataobject

Event-specific content. See the page for each event.

The webhook
{
  "id": "4b8e2f6a-9c3d-4e7b-a1f5-8d2c6b9e0a47",
  "event": "completed_lead",
  "webhookVersion": "2026-07-01",
  "createdAtUtc": "2026-07-28T14:32:11.482Z",
  "agency": {
    "id": "c1a2b3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Acme Marketing"
  },
  "subaccount": {
    "id": "7f3e9d21-4b8a-4c5e-9f60-2d8b1a7c3e54",
    "name": "Acme Fitness Client"
  },
  "form": {
    "id": "e8b4c672-9a1d-4f3b-b7c8-5d2e9f0a1b3c",
    "name": "Fitness Coaching Application",
    "url": "https://forms.roasform.com/fitness-coaching"
  },
  "data": {}
}

Request headers

HeaderExampleMeaning
Content-Typeapplication/jsonBody is JSON.
User-AgentROASForm-Webhooks/1Identifies ROASForm.
X-ROASForm-Eventcompleted_leadThe event type.
X-ROASForm-Delivery4b8e2f6a-…Same as the webhook's id.
X-ROASForm-Attempt21 on the first try, higher on retries.
X-ROASForm-Signaturet=1753712000,v1=5f8a…HMAC signature. See Verifying signatures.

Shared objects

These shapes appear inside data across events. A field that has no value is null, never omitted.

lead

The lead is the person, not the run: leads are matched across submissions by email or phone within the subaccount. Contact fields captured by this submission are merged into the saved record before delivery, so the object arrives as complete as we know the person — a returning visitor's company from an earlier form shows up even if this form never asked for it, tags accumulate across runs, and createdAtUtc is when the person was first seen. For what this specific run captured, read submission.answers.

iduuid
Lead public id.
namenullable string
emailnullable string
phonenullable string
E.164 where available.
companynullable string
locationnullable string
tagsstring[]
The lead's accumulated tags.
timezonenullable string
IANA zone, e.g. America/Chicago.
countrynullable string
ISO country code.
createdAtUtctimestamp
When the lead record was created.
lead
{
  "id": "3d9f7a25-6c1b-4e8d-a2f4-8b0c5d7e9f13",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "phone": "+15555550100",
  "company": "Doe Fitness LLC",
  "location": "Austin, TX",
  "tags": ["hot-lead"],
  "timezone": "America/Chicago",
  "country": "US",
  "createdAtUtc": "2026-07-28T14:29:03.114Z"
}

submission

iduuid
statusstring
Possible values
partialcomplete
qualificationstring

The qualification outcome of this submission, set by the ending reached; none while partial.

Possible values
nonequalifiedunqualified
scorenumber
Score at delivery time.
startedAtUtctimestamp
completedAtUtcnullable timestamp
null while partial.
endingnullable object

Which ending screen the visitor reached — the exit path's identity. Screen content and redirect URLs are builder configuration and are not delivered. null while partial. If the ending was later deleted in the builder, id is null and name/type come from the values saved with the submission — so route on type, match exact endings on id, and treat id: null as an ending that no longer exists in the builder.

+Show child attributes
idnullable uuid
namestring
The ending's name in the builder.
typestring
Possible values
calendarredirectsuccess
lastSeenElementnullable object

The question the visitor was on most recently, in the same shape as an answers entry. For abandoned submissions this is where they stopped; value, values, and score are null when that question was never answered.

answersarray

One entry per answered question, in the order each question was first answered (the visitor's path through the form, which can differ from the builder order when logic jumps). An answer edited later keeps its original position. See the answer shape below.

appliedTagsstring[]

Tags this submission applied.

utmDatanullable object

UTM parameters captured with the submission.

ipAddressnullable string

The visitor's IP address.

userAgentnullable string

The visitor's browser user agent.

answers[]

elementIduuid
The question element.
labelstring
Question text as answered.
typestring

The question's element type. Forms created long ago may also send the legacy types single_select and multi_select.

Possible values
nameemailphoneaddresswebsitetextlong_textshort_textmultiple_choicesingle_choiceimage_choicedropdownyes_noopinion_scaleratingnumberdatefile_upload
valuenullable string

Single-value answers. null for multi-select answers.

valuesnullable string[]

Multi-select answers. null otherwise.

scorenullable number
null for unscored questions.
submission
{
  "id": "5a2c8e94-1f7d-4b3a-9e6c-4d8f0b2a6c71",
  "status": "complete",
  "qualification": "qualified",
  "score": 40,
  "startedAtUtc": "2026-07-28T14:26:40.207Z",
  "completedAtUtc": "2026-07-28T14:32:11.482Z",
  "ending": {
    "id": "9c4b7d13-8e2a-4f6c-b1d9-7a3e5c8f2d46",
    "name": "Qualified - Book a call",
    "type": "calendar"
  },
  "lastSeenElement": {
    "elementId": "a1d4c7f2-9b3e-4c6a-8d2f-5e9b1c4a7d30",
    "label": "What is your email?",
    "type": "email",
    "value": "jane@example.com",
    "values": null,
    "score": null
  },
  "answers": [
    {
      "elementId": "b7e2f9a4-3c8d-4a1e-9f5b-6d0c2e8a4f17",
      "label": "What is your monthly budget?",
      "type": "single_choice",
      "value": "$2,000 - $5,000",
      "values": null,
      "score": 30
    },
    {
      "elementId": "d2c8f5b1-7e4a-4d9c-a6f3-9b0e5d8c2a64",
      "label": "What are your goals?",
      "type": "multiple_choice",
      "value": null,
      "values": ["Lose weight", "Build muscle"],
      "score": 10
    }
  ],
  "appliedTags": ["hot-lead"],
  "utmData": {
    "utm_source": "facebook",
    "utm_medium": "cpc",
    "utm_campaign": "summer-2026"
  },
  "ipAddress": "203.0.113.42",
  "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15"
}

Data freshness

Deliveries are built by reading the database at send time, not from a frozen snapshot — so most payloads (delivered within seconds of the event) are effectively point-in-time, but a retried delivery reflects anything that changed in between. What that means per field:

Frozen at the eventLive at delivery time
id, createdAtUtc, webhookVersionthe whole lead object (current state of the person)
submission.startedAtUtc / completedAtUtc, qualification, utmData, ipAddress, userAgentform.name / form.url, agency and subaccount names
answer texts and values (saved as answered — later question renames don't rewrite them)ending.name (renames show; deletion falls back to the saved snapshot)
the ending's type (survives even deletion via the snapshot)appointment (a reschedule before delivery shows the new time)

On events for still-partial submissions (form_step, partial_lead), a retried delivery can also reflect further progress the visitor made in the meantime — treat every delivery as a fresh snapshot keyed by submission.id.