FillioDocs

Webhooks

Send every submission to an address of your own, as JSON, the moment it arrives.

One POST per response

Sent as soon as the answer is stored, without keeping the respondent waiting.

A stable JSON shape

Public type names and permanent field IDs, so renaming a question never breaks your code.

10 seconds, one attempt

Fillio waits ten seconds for your endpoint and does not try again afterwards.

Turning a webhook on

Open the form, go to the Settings tab and switch Webhook on. Paste the address that should receive the submissions; Fillio saves it a moment after you stop typing and confirms it on screen. Each form carries one address, and switching the toggle back off clears it.

The address has to be HTTPS

Fillio only calls addresses that begin with https://. An http:// address is accepted by the settings field but is never called, so a webhook that appears to be saved will silently deliver nothing.

Addresses that point inside a private network are refused as well: any host on 10.x, 127.x, 169.254.x, 172.16-31.x or 192.168.x, any IPv6 address, and any name that is localhost or ends in .local or .internal. Your endpoint has to be reachable from the public internet.

The request

A POST with a JSON body. Four headers travel with it.

HeaderDescription
Content-Type: application/jsonThe body is always JSON.
User-Agent: Fillio Webhooks/1.0Identifies the caller as Fillio, which is useful when one endpoint listens to several services.
X-Fillio-Event: FORM_RESPONSEThe event name. FORM_RESPONSE is the only value Fillio sends today.
X-Fillio-Event-IdA fresh UUID for this delivery, repeated as eventId in the body.

The payload

An envelope describing the event, and a data object describing the response.

{
  "eventId": "8f2c1d7a-4b09-4e5c-9a13-6d0f8b2e77c4",
  "eventType": "FORM_RESPONSE",
  "createdAt": "2026-08-26T09:14:22.481Z",
  "data": {
    "responseId": "Qh3Rk9wZm1sVtY7cN2xP",
    "formId": "7bTn4Kq2Wx",
    "formName": "Workshop signup",
    "createdAt": "2026-08-26T09:14:22.481Z",
    "fields": [
      {
        "key": "V1StGXR8_Z5jdHi6B-myT",
        "label": "Full name",
        "type": "INPUT_TEXT",
        "value": "Mira Kaya"
      },
      {
        "key": "kM4pQz7LxAe2Rn0BsTvUw",
        "label": "Which session?",
        "type": "MULTIPLE_CHOICE",
        "value": "c9XbNf3JdQ",
        "options": [
          { "id": "c9XbNf3JdQ", "text": "Morning" },
          { "id": "eR2mYt8KpL", "text": "Afternoon" }
        ]
      },
      {
        "key": "zA6cWn1FyH4tXd9QrLbEs",
        "label": "How likely are you to recommend us?",
        "type": "LINEAR_SCALE",
        "value": 9
      }
    ]
  }
}
PropertyDescription
eventIdA unique ID for this delivery.
eventTypeAlways FORM_RESPONSE.
createdAtWhen the response was submitted, as an ISO 8601 timestamp in UTC.
data.responseIdThe ID of the stored response.
data.formIdThe ID of the form, the same one that appears in its public link.
data.formNameThe form title as plain text, with any formatting removed.
data.fieldsOne entry per answered question.

Inside fields

Every entry carries four properties, and a question with options carries a fifth.

PropertyDescription
keyThe question's permanent ID. It survives every edit to the wording, so match on this rather than on the label.
labelThe question as written in the editor.
typeThe question type, taken from the list below.
valueThe answer, in the shape that question stores.
optionsOnly on questions with options: every option's ID and text, so you can turn an answer back into a label.

Only answered questions arrive

A question the respondent left untouched is absent from the array rather than sent as an empty value, and a question hidden by conditional logic never appears at all. Look each answer up by its key instead of relying on position or on a fixed number of entries.

Choice answers carry IDs, not labels

A dropdown or multiple choice answer is the chosen option's ID, and a checkbox, multi-select or ranking answer is an array of option IDs. Resolve them against the options list on the same entry. A ranking answer keeps the order the respondent put the items in.

The Other option

When a respondent writes into an Other box, the value is not an option ID but the text __OTHER__: followed by what they typed. Strip that prefix to read the answer.

"value": "__OTHER__:Heard about it from a friend"

Files and signatures

A file upload answer is a download link to the file in Fillio's storage. A signature answer is a base64 PNG data URL, which you can save or display directly.

Field types

The type is a stable public name, so a change inside Fillio never reaches your integration. Anything Fillio cannot map arrives as INPUT_TEXT.

TypeQuestionValue
INPUT_TEXTShort AnswerText
TEXTAREALong AnswerText
INPUT_EMAILEmailText
INPUT_PHONE_NUMBERPhone NumberText
INPUT_NUMBERNumberThe number as text, exactly as it was typed
INPUT_LINKLinkText
INPUT_DATEDateA date, as YYYY-MM-DD
INPUT_TIMETimeA time, as HH:MM on a 24-hour clock
DROPDOWNDropdownOne option ID
MULTIPLE_CHOICERadioOne option ID
CHECKBOXESCheckboxAn array of option IDs
MULTI_SELECTMulti-SelectAn array of option IDs
RATINGStar RatingA number
LINEAR_SCALELinear ScaleA number
RANKINGRankingAn array of option IDs, in the respondent's order
FILE_UPLOADFile UploadA download link
SIGNATURESignatureA base64 PNG data URL

If your endpoint does not answer

Fillio waits ten seconds. A timeout, a connection error or any status outside the 2xx range counts as a failed delivery, and that is the end of it: there is no retry and no queue to replay from.

The response itself is never affected. It is stored before the request is made and stays in your dashboard whatever your endpoint does, so the dashboard remains the complete record even if a delivery is lost.

There is no signature

Fillio does not sign the request and sends no shared secret, so the body on its own does not prove where it came from. If your endpoint has to be certain, give it an address only you know: a long, unguessable path or query string. Treat anything arriving elsewhere as untrusted.

Each delivery carries its own event ID, in both the X-Fillio-Event-Id header and the body. Recording the ones you have already handled is the simplest way to make your endpoint safe to call twice.

Tip

Point the webhook at any request-inspection endpoint and submit your own form once. You will see the exact payload your form produces, with your own field IDs in it. That is the fastest way to write the mapping on your side.