# Form Ingest

Form ingest turns a webhook into a browser-friendly submission endpoint. It is useful for lightweight embeds, back-office tools, or systems that can only post traditional HTML forms instead of signed JSON requests.

## Form channel model

The public form URL follows this shape:

```text
https://hooks.hooksbase.com/v1/form/{publicId}
```

The webhook's `publicId` (`hook_...`) is returned in the create-webhook response as `formIngestUrl`. No separate secret is required — the unguessable `publicId` is the access credential, so rotate the webhook if the URL leaks.

The endpoint supports both:

- a minimal HTML form page (`GET`) for quick testing or lightweight embeds
- real form submissions (`POST`) that become deliveries on the target webhook

## Auth model

- **Public form route**: `GET`, `POST`, and `OPTIONS` on `/v1/form/{publicId}` are public. The route relies on the unguessable `publicId`.
- **Dashboard**: There is no separate dashboard-only form runtime. You create or inspect the webhook through the normal webhook surfaces and then publish the form URL.
- **SDK / CLI**: The first-party SDK and CLI do not expose form-specific helpers yet.

## Content types and CORS

Supported request content types are:

- `application/x-www-form-urlencoded`
- `multipart/form-data`

Anything else returns `415`.

POST and OPTIONS responses on the form route send permissive CORS headers:

- `Access-Control-Allow-Origin: *`
- `Access-Control-Allow-Methods: POST, OPTIONS`
- `Access-Control-Allow-Headers: Content-Type`

Use `OPTIONS /v1/form/{publicId}` for preflight requests.

## Payloads and files

Hooksbase normalizes the submission into a JSON envelope:

- `source: "form"`
- `fields` for scalar and multi-value form fields
- optional `files` for uploads

Field coercion rules:

- a single submitted value stays a string
- repeated field names become arrays

File behavior:

- per-file limit: `15 MB`
- per-ingest total: `50 MB`
- when file storage is available, each file gets a `fileRef`
- free-tier projects keep file metadata only

Status behavior follows normal ingest:

- `404` for archived or missing webhooks
- `409` for paused webhooks or disabled projects

## Related routes

- `GET /v1/form/{publicId}`
- `POST /v1/form/{publicId}`
- `OPTIONS /v1/form/{publicId}`
- `GET /v1/files/{signedToken}`
- `GET /v1/webhooks/{webhookId}/deliveries/{deliveryId}/files/{index}`

## Common mistakes

- Treating the form endpoint like a general JSON ingest route. It only accepts classic form content types.
- Forgetting to handle repeated field names as arrays.
- Assuming browser uploads bypass the same webhook paused and project-disabled checks as other ingest channels.
- Expecting file content persistence on the free tier, where only metadata is retained.
