> For the complete documentation index, see [llms.txt](https://docs.insurgrid.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.insurgrid.com/intake-forms/b-embed-form.md).

# Quickstart B — Share a form and receive submissions

Get a published form in front of customers, and get the completed submissions back.

Once a form is published it has a link. Send that link to a customer and they can fill the form in — that's the whole of sharing it.

What happens next is the part worth setting up. Every completed form shows up in your InsurGrid dashboard automatically. If you also want the submission delivered to your own systems — every answer, the contact details, any uploaded files, and the filled ACORD PDFs — you register a webhook endpoint, and InsurGrid posts each one to you as it arrives.

This page covers both halves. About 20 minutes for the webhook half; sharing the link takes a minute.

**What you'll need**

* An InsurGrid account with at least one **published** form.
* For the webhook half: an HTTPS address InsurGrid can reach, that accepts `POST`. A deployed server is simplest — if you're working on your laptop, it isn't reachable from the internet, so put a tunnel in front of it ([how](/get-started/05-local-development.md)).

|            | Staging                                 | Production                      |
| ---------- | --------------------------------------- | ------------------------------- |
| API        | `https://phoenix-staging.insurgrid.com` | `https://phoenix.insurgrid.com` |
| Form links | `https://link-staging.insurgrid.com`    | `https://link.insurgrid.com`    |

***

## Part one — share the form

### Get the link

Every published form has a **`shareUrl`**. It's in the dashboard's **Forms** section, or fetch it:

```bash
curl 'https://phoenix-staging.insurgrid.com/api/v1/forms' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

```
https://link-staging.insurgrid.com/jane-smith/form/f_9c2a7b1e4d/apartment-intake-acme
```

> ⚠️ **`shareUrl` is `null` until the form is published.** Drafts and archived forms have no link at all. Publish first, with `PUT /api/v1/forms/{id}/publish`.

The first segment is the **slug of the agent who created the form**, and that's who submissions through this link are credited to. It appears again as `agent` in every webhook.

### Give it to people

It's an ordinary web address, so use it however suits you — email or text it to a customer, put it behind a button on your website, print it as a QR code. InsurGrid hosts the page; nothing needs registering beforehand.

### Showing it inside your own page

If you'd rather the form appeared within your product than on a page of its own, put the link in an `<iframe>` and add `?embed=1`. That flag strips InsurGrid's header and page furniture, so only the form shows — no InsurGrid branding around it.

```html
<iframe
  src="https://link-staging.insurgrid.com/jane-smith/form/f_9c2a7b1e4d/apartment-intake-acme?embed=1"
  title="Apartment Intake"
  width="100%"
  height="800"
  style="border:0;"
  loading="lazy">
</iframe>
```

Give the frame a height that suits your layout — the form doesn't message your page, so there's nothing to listen for and nothing to resize against. Everything else works exactly as it does through a plain link.

***

## Part two — receive the submissions

When someone completes the form, the response lands in your InsurGrid dashboard straight away. That happens for every account with nothing to set up — you can read the answers, download any files, and review the submission there.

To get the same submission into **your own** systems, register a webhook endpoint. InsurGrid then posts each completed submission to your server as a `form-submission` event, carrying the contact details, every answer, links to any uploaded files and, for ACORD-mapped forms, a download of the filled PDFs.

The rest of this page is that setup.

### Register your endpoint

In the InsurGrid **webhooks dashboard**:

1. **Add your endpoint** — a publicly reachable HTTPS URL that accepts `POST`. Each account has one.
2. **Choose your events** — tick **`form-submission` · intake & ACORD**. You can receive `client-submission` (policy data) on the same endpoint too.
3. **Choose how InsurGrid authenticates to you** — see below.
4. **Send a test event** to check your handler before real traffic arrives.

### How InsurGrid proves it's InsurGrid

You pick this per endpoint; it isn't fixed:

| Option                | What your endpoint receives                                                                   |
| --------------------- | --------------------------------------------------------------------------------------------- |
| **Default signature** | Every request signed by InsurGrid. No setup needed.                                           |
| **Custom signature**  | An HMAC-SHA256 signature of the body, in a header you name, signed with a secret you provide. |
| **API key**           | Your key, in a header or query parameter you name.                                            |
| **Bearer token**      | `Authorization: Bearer <token>`                                                               |
| **Basic auth**        | `Authorization: Basic <credentials>`                                                          |

We recommend **Custom signature**, **API key** or **Bearer token**, so your server can reject anything it can't verify.

A staging secret never validates a production event, and vice versa.

### Acknowledge first, work second

Return any `2xx` as soon as the event arrives — the body is ignored. Do the slow work after you've responded.

```js
app.post("/hooks/insurgrid", (req, res) => {
  if (!verifyHoweverYouConfiguredIt(req)) return res.sendStatus(401);
  res.sendStatus(200);               // acknowledge
  handleSubmission(req.body.data);   // then process
});
```

Anything else — another status, a timeout, no response — counts as a failed delivery and is retried. You can also resend any event from the dashboard.

Two behaviours to design for:

* **Events can arrive more than once.** Use `data.submission_id` to skip ones you've handled.
* **Order isn't guaranteed.** If sequence matters, sort on `data.submitted_at`.

***

## What the event looks like

Every event has the same envelope — check `topic` first, since one endpoint can receive several kinds:

```json
{ "topic": "form-submission", "data": { … } }
```

### Inside `data`

| Field                   | Type           | What it is                                                                                                                             |
| ----------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `submission_id`         | integer        | Unique id for this submission. Your de-duplication key.                                                                                |
| `trace_id`              | string \| null | The form session that produced it. Quote it if you contact support.                                                                    |
| `submitted_at`          | string         | When it was submitted, UTC ISO 8601.                                                                                                   |
| `form.id`               | integer        | Form id.                                                                                                                               |
| `form.public_id`        | string         | The public id used in links and embeds.                                                                                                |
| `form.name`             | string         | Form name.                                                                                                                             |
| `form.insurance_line`   | string         | `personal` or `commercial`.                                                                                                            |
| `form.external_user_id` | string \| null | Your own user id, if the form was created through the Forms API with one.                                                              |
| `agent`                 | object         | Who it's credited to: `id`, `name`, `email`. Name and email can be `null`.                                                             |
| `contact`               | object         | The submitter's `name`, `email`, `phone`. Filled from questions carrying a `field_mapping` — see the warning below. Any can be `null`. |
| `answers`               | array          | One entry per question. See below.                                                                                                     |
| `acord`                 | object         | ACORD output. See below.                                                                                                               |

> ⚠️ **`contact` only fills if the questions are tagged.** InsurGrid reads it from questions carrying a `field_mapping` of `FullName`, `Email` or `Phone` — never from a question's type or label. A form built through the API with its own `steps`, and no tags, delivers `contact: {name: null, email: null, phone: null}` on every submission, with the answers still present in `answers[]`. See [Keeping the contact block populated](/intake-forms/forms.md#keeping-the-contact-block-populated).

### Answers

`answers` lists **every question on the form, in order — including ones nobody answered**, which carry `value: null`.

| Field         | What it is                                                              |
| ------------- | ----------------------------------------------------------------------- |
| `question_id` | Stable id. **Map on this, not `label`** — labels get edited, ids don't. |
| `label`       | The question text as shown to the submitter.                            |
| `type`        | The question type.                                                      |
| `value`       | The answer. Shape depends on `type`.                                    |
| `columns`     | `data_table` only — the table's columns (`id`, `label`, `type`).        |

| Type                               | `value`                                                     |
| ---------------------------------- | ----------------------------------------------------------- |
| `short_text`, `long_text`, `email` | string                                                      |
| `number`                           | number                                                      |
| `date`                             | date string                                                 |
| `dropdown`, `single_choice`        | string — the option they picked                             |
| `multi_choice`                     | array of strings                                            |
| `address`                          | object: `line1`, `line2`, `city`, `state`, `zip`, `country` |
| `data_table`                       | array of rows, each keyed by column id                      |
| `file_upload`                      | array of `{ filename, content_type, url }`                  |

> ⚠️ **File links are public and permanent — treat them as sensitive.** The `url` on a `file_upload` answer, and `acord.archive_url`, are unsigned links to a public bucket. They don't expire, and **anyone who has the URL can download the file without authenticating**. Since these are documents your customer uploaded, don't paste one into a ticket, a chat, an email or a log you wouldn't paste the document itself into.
>
> Download and store what you need when the event arrives: there is no endpoint to re-fetch a submission, and a file's `url` is `null` once the document is no longer available.

### ACORD

| Field                  | What it is                                                                                           |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `acord.is_acord_ready` | `true` when the form is mapped to ACORD forms.                                                       |
| `acord.form_ids`       | The ACORD numbers filled from this submission, e.g. `["125", "140"]`. Empty when not ACORD-ready.    |
| `acord.archive_url`    | A ZIP of the filled PDFs. Present only when ACORD-ready *and* the archive built — treat as optional. |

<details>

<summary>A complete example event</summary>

```json
{
  "topic": "form-submission",
  "data": {
    "form": {
      "id": 4412,
      "public_id": "f_9c2a7b1e4d",
      "name": "Apartment Intake — Acme Agency",
      "insurance_line": "commercial",
      "external_user_id": "partner-user-123"
    },
    "submission_id": 55231,
    "trace_id": "tr_demo5f3c9a2b",
    "submitted_at": "2026-08-24T18:22:40Z",
    "agent": { "id": 88213, "name": "Jane Smith", "email": "jane@acme-agency.com" },
    "contact": { "name": "Jimmy Swanson", "email": "jimmy.swanson@example.com", "phone": "(555) 010-1234" },
    "answers": [
      { "question_id": "b1c2-demo", "label": "Full name", "type": "short_text", "value": "Jimmy Swanson" },
      { "question_id": "c3d4-demo", "label": "Property address", "type": "address",
        "value": { "line1": "12 Oak St", "line2": "", "city": "Austin", "state": "TX", "zip": "78701", "country": "US" } },
      { "question_id": "e5f6-demo", "label": "Drivers", "type": "data_table",
        "value": [ { "col-name-demo": "Jimmy Swanson", "col-license-demo": "TX", "col-dob-demo": "1989-04-12" } ],
        "columns": [
          { "id": "col-name-demo", "label": "Driver name", "type": "short_text" },
          { "id": "col-license-demo", "label": "License state", "type": "dropdown" },
          { "id": "col-dob-demo", "label": "Date of birth", "type": "date" }
        ] },
      { "question_id": "d4e5-demo", "label": "Loss runs", "type": "file_upload",
        "value": [ { "filename": "losses.pdf", "content_type": "application/pdf",
                     "url": "https://s3.us-east-2.amazonaws.com/<bucket>/documents/…/losses.pdf" } ] },
      { "question_id": "f7a8-demo", "label": "Prior claims in the last 5 years?",
        "type": "single_choice", "value": null }
    ],
    "acord": {
      "is_acord_ready": true,
      "form_ids": ["125", "140", "126"],
      "archive_url": "https://s3.us-east-2.amazonaws.com/<bucket>/acord/…/archive.zip"
    }
  }
}
```

</details>

### Handling a repeat delivery

```python
def handle_submission(data):
    sid = data["submission_id"]
    if already_processed(sid):
        return
    mark_processed(sid)
    save(data)
```

***

## Next steps

* [Build & manage forms via the API](/intake-forms/c-build-forms-api.md)
* [Embed the form builder](/intake-forms/builder-sessions.md) — let your users build their own forms
* [Submissions](/intake-forms/submissions.md)
* [Environments](/guides/environments.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.insurgrid.com/intake-forms/b-embed-form.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
