ROASFormDocs
Webhooks

The webhook

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, in the changelog. New fields are additive; build your integration to ignore fields it does not recognize, so additions never affect it.

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 version of the payload contract this delivery follows.

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
urlnullable string
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-08-05",
  "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.
ROASForm-Eventcompleted_leadThe event type.
ROASForm-Delivery4b8e2f6a-…Same as the webhook's id.
ROASForm-Attempt21 on the first try, higher on retries.
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 the moment the event fired.
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 had already been deleted when the event fired, 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. Becomes null entirely if that question had already been deleted when the event fired.

answersarray

One entry per question the visitor answered or moved past, in the order each was first recorded (the visitor's path through the form, which can differ from the builder order when logic jumps). An entry with no answer content (value null or empty, values null or empty) is a question that was presented and left unanswered; questions the visitor never reached don't appear at all. 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[]

elementIdnullable uuid

The question element. null if the question had already been deleted when the event fired; label, type, and the answer values are saved with the answer, so they survive the deletion.

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. Two types carry a JSON-encoded string that needs a second parse: file_upload decodes to { url, name, size, type }, where url is a permanent public link to the uploaded file (the payload carries the pointer, never the bytes, and the link does not expire); a structured address decodes to the address components alongside a formatted string.

valuesnullable string[]

Multi-select answers. null otherwise.

scorenullable number

The points this answer contributed. Only questions that participate in scoring (at least one scoring rule targets them) carry a number, so 0 always means a scoreable answer that earned nothing. Everything else is null: questions outside scoring, questions never answered, and answers recorded mid-progress that scoring (which runs at contact capture and submit) has not evaluated yet.

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

Every delivery is a snapshot of the moment the event happened. The body is captured when the event fires (the createdAtUtc moment) and frozen: later changes, a renamed form, a lead updated by a newer submission, a rescheduled appointment, a question deleted in the builder, never rewrite a delivery that was already captured. Retries resend the same body byte for byte.

Because each delivery is point-in-time, the newest delivery for a submission.id is the current state of that submission, and the lead object inside it reflects everything known about the person up to that moment.