# Fillout Webhooks

[Fillout](https://www.fillout.com/) is a form builder (forms, surveys, quizzes and
scheduling). A Fillout webhook is a form integration: when someone submits the form,
Fillout sends the submission to your URL as JSON.

## When to Use This Skill

* How do I receive Fillout form submissions with a webhook?
* How do I verify a Fillout webhook signature? (You can't. Fillout doesn't sign webhooks.
  Authenticate with a custom header instead.)
* How do I protect my Fillout webhook endpoint with `Authorization: Bearer <token>`?
* How do I read answers from the `questions` array in a Fillout webhook payload?
* How do I handle Fillout scheduling form bookings or payments in a webhook?
* Why does my Fillout webhook only send some fields? (Advanced mode replaces the full
  submission with your own mapped body.)

## There Is No Signature

Fillout's [webhook docs](https://www.fillout.com/help/webhook) describe no signature
header, no signing secret, no timestamp and no HMAC. Don't invent an
`X-Fillout-Signature` verifier: no real delivery would pass it.

The documented verification mechanism is the optional **Headers** section of the
webhook's **Advanced** settings. Fillout's docs give this as the typical use:

> The most typical use case for headers is if you have some verification required for
> your endpoint, and you have a special key you want to send, which will look something
> like: `Authorization —> Bearer <secret_token>`

So you:

1. Generate a random token yourself (`openssl rand -hex 32`).
2. In Fillout, open **Integrate → Webhook → Advanced → Headers** and add
  `Authorization` = `Bearer <token>`.
3. On your endpoint, compare the **whole** `Authorization` header value with
  `Bearer <token>` in constant time. Return `401` if it's missing or wrong.
4. Fail closed: if the token isn't configured on your server, reject the request.

This proves the caller knows the shared secret. It does not protect the body from
tampering, since nothing is signed over it. Because nothing is signed over the body,
you don't need the raw body either: parsing JSON before checking the header is fine.

## Verification (core)

```javascript
const crypto = require('crypto');

// Fillout sends the header you configured: Authorization: Bearer <token>.
// Compare the WHOLE header value, not just the part after "Bearer ".
function verifyFilloutAuth(authorizationHeader, token) {
  if (!token) return false; // fail closed when FILLOUT_WEBHOOK_TOKEN is unset
  if (typeof authorizationHeader !== 'string') return false;
  // Hash both sides so timingSafeEqual always gets equal-length buffers
  const a = crypto.createHash('sha256').update(authorizationHeader).digest();
  const b = crypto.createHash('sha256').update(`Bearer ${token}`).digest();
  return crypto.timingSafeEqual(a, b);
}

// verifyFilloutAuth(req.headers.authorization, process.env.FILLOUT_WEBHOOK_TOKEN)

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fillout-webhooks/examples/express/),
> [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fillout-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fillout-webhooks/examples/fastapi/).

## Events: Form Submission Only

Fillout webhooks have **no event type field**. A webhook added under **Integrate** runs
once, when the form is submitted. On a scheduling form that means the original booking;
Fillout's docs say to use a workflow for cancellations and reschedules.

In **Simple** mode (the default), Fillout sends the full submission. The documented
submission object (the same shape the REST API returns, and what API-created webhooks
send) looks like this:

```json
{
  "submissionId": "6bcedd3a-fe26-4bcb-9ce9-d3850e38d42c",
  "submissionTime": "2026-10-21T10:15:30.000Z",
  "lastUpdatedAt": "2026-10-21T10:15:30.000Z",
  "questions": [
    { "id": "abc1", "name": "What's your email?", "type": "EmailInput", "value": "ada@example.com" }
  ],
  "calculations": [],
  "urlParameters": [{ "id": "utm_source", "name": "utm_source", "value": "newsletter" }]
}

```

Optional members: `scheduling`, `payments`, `quiz` and `login`. The examples also accept
the submission wrapped as `{ "submission": { ... } }`.

**Headers live in Advanced mode, and Advanced mode changes the body.** Fillout's docs:
*"In Advanced, Fillout no longer sends the full submission. You map the data yourself in
the Body section."* So a webhook that sends the `Authorization` header also sends a flat
JSON object of keys you chose. Map a `submissionId` key to Fillout's **Submission ID**
value so you can deduplicate, then map the answers you need:

```json
{ "submissionId": "6bcedd3a-fe26-4bcb-9ce9-d3850e38d42c", "email": "ada@example.com", "name": "Ada Lovelace" }

```

The examples handle both shapes: a full submission (with a `questions` array) and a
flat mapped body.

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fillout-webhooks/references/overview.md) for the payload fields.

## Environment Variables

```bash
# The token YOU generated. Fillout must send: Authorization: Bearer <this value>
FILLOUT_WEBHOOK_TOKEN=generate_with_openssl_rand_hex_32

```

## Local Development

```bash
npx hookdeck-cli listen 3000 fillout --path /webhooks/fillout

```

No account required. The CLI creates a guest account on first run and gives you a public
URL and a web UI for inspecting requests. Use port `8000` for the FastAPI example.

Hookdeck's Fillout source type checks the header for you. Set **Header Name** to
`Authorization` and **Header Value** to the full value, including `Bearer `.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fillout-webhooks/references/overview.md) - How Fillout webhooks work, payload fields, Simple vs Advanced mode
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fillout-webhooks/references/setup.md) - Adding the webhook and the Authorization header in Fillout, the REST API, retries
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fillout-webhooks/references/verification.md) - Header authentication per framework, gotchas, debugging