> 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/quickstarts/a-receive-policy-data.md).

# Quickstart A — Receive policy data via webhook

**What you'll build:** a small web server that receives extracted policy data from InsurGrid, verifies it's genuine, and prints it. By the end you'll have handled a real (test) policy-data webhook end to end.

**Time:** \~20 minutes.

**Prerequisites** (from [Get started](/get-started/01-what-you-can-build.md)):

* [ ] An InsurGrid account with access to the **API section**.
* [ ] Your **signing secret** (from [API credentials](/get-started/03-api-credentials.md)).
* [ ] Node 18+ *or* Python 3.9+ installed.
* [ ] [ngrok](/get-started/05-local-development.md) (or any tunnel) if you're on your local machine.

***

## Step 1 — Get your signing secret

From the dashboard's **API section**, copy your **signing secret** and put it in an environment variable so it never lands in your code:

```bash
export INSURGRID_SIGNING_SECRET="whsec_...your secret..."
```

You'll use it in Step 4 to prove each webhook really came from InsurGrid.

## Step 2 — Stand up an endpoint · *server*

Create a tiny server with one route that accepts a `POST`. Read the **raw request body** as well as the parsed JSON — you need the raw bytes for signature verification in Step 4.

**Node (Express):**

```js
// server.js — run with: node server.js
const express = require("express");
const app = express();

// Keep the raw body; we need the exact bytes to verify the signature.
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; },
}));

app.post("/hooks/insurgrid", (req, res) => {
  res.sendStatus(200);          // Step 3: acknowledge fast
  console.log("Received:", req.body.topic);
  // signature check + processing come next
});

app.listen(3000, () => console.log("Listening on :3000"));
```

**Python (Flask):**

```python
# server.py — run with: flask --app server run --port 3000
from flask import Flask, request

app = Flask(__name__)

@app.post("/hooks/insurgrid")
def insurgrid_hook():
    raw = request.get_data()          # exact bytes, for the signature check
    event = request.get_json()
    print("Received:", event["topic"])
    return "", 200                    # Step 3: acknowledge fast
```

## Step 3 — Acknowledge fast, process later

Notice both handlers return `200` right away. Do any slow work (saving to your DB, calling other services) *after* you respond — see [Set up a webhook endpoint](/get-started/04-webhook-endpoint.md) for why this matters. For this quickstart, printing is enough.

## Step 4 — Expose your endpoint & register it · *local dev*

If you're on your machine, start a tunnel so InsurGrid can reach you:

```bash
ngrok http 3000
```

Copy the `https://…` URL it prints, append `/hooks/insurgrid`, and add it in the dashboard's **API section** as the **staging** URL for the `client-submission` topic. (Full walkthrough: [Expose your local endpoint](/get-started/05-local-development.md).)

## Step 5 — Verify the signature · *server*

Every webhook carries an `x-signature` header: an HMAC-SHA256 of the **raw request body**, computed with your signing secret. Recompute it on your side and compare — if it doesn't match, reject the request.

> **New term — HMAC signature:** a fingerprint of the message made with a shared secret. Because only you and InsurGrid know the secret, a matching fingerprint proves the message is genuine and unaltered. **Compute it over the exact raw bytes you received** — if you re-serialize the parsed JSON first, the bytes differ and the check will falsely fail. This is the #1 first-timer bug.

**Node:**

```js
const crypto = require("crypto");

function verify(rawBody, signature, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  // timing-safe compare avoids leaking info via response time
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ""));
}

app.post("/hooks/insurgrid", (req, res) => {
  const ok = verify(req.rawBody, req.get("x-signature"), process.env.INSURGRID_SIGNING_SECRET);
  if (!ok) return res.sendStatus(401);   // not from InsurGrid — reject
  res.sendStatus(200);
  handle(req.body);
});
```

**Python:**

```python
import hmac, hashlib, os

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/hooks/insurgrid")
def insurgrid_hook():
    raw = request.get_data()
    if not verify(raw, request.headers.get("x-signature", ""),
                  os.environ["INSURGRID_SIGNING_SECRET"]):
        return "", 401                    # not from InsurGrid — reject
    handle(request.get_json())
    return "", 200
```

> Prefer a different scheme (Bearer token, Basic auth, or an API-key header) instead of a signature? You can configure that per endpoint — see Register your endpoint & choose an auth type.

## Step 6 — Read the payload · *server*

A verified policy-data webhook looks like this:

```json
{
  "topic": "client-submission",
  "data": {
    "document_link": "https://files.insurgrid.com/…/declaration.pdf",
    "policy_type": "Personal Auto",
    "policy": { "provider": "…", "policy_number": "…" },
    "policyholder": { "first_name": "…", "last_name": "…" },
    "vehicles": [ … ],
    "…": "…extracted fields, depending on policy_type…"
  }
}
```

Key fields:

| Field                | Type         | Meaning                                                                                   | Example                           |
| -------------------- | ------------ | ----------------------------------------------------------------------------------------- | --------------------------------- |
| `topic`              | string       | The event type. For policy data it's `client-submission`.                                 | `"client-submission"`             |
| `data.document_link` | string (URL) | A **time-limited** link to the source declaration-page file.                              | `"https://files.insurgrid.com/…"` |
| `data.policy_type`   | string       | A descriptive label for the insurance line — a hint, not a fixed code (don't hard-match). | `"Personal Auto"`, `"Homeowners"` |
| `data.*`             | object/array | The extracted policy fields. The set depends on `policy_type`.                            | see Webhook events & payloads     |

> ⚠️ **`document_link` expires.** It's a temporary download link. If you need the file, fetch and store it on your side promptly — don't save the link and expect it to work later.

The full field list per policy type is in the Webhook events & payloads reference.

## Step 7 — De-duplicate · *server*

Delivery is **at-least-once**: the same event may occasionally arrive more than once (for example, if a retry overlaps a slow success). So make your handler **idempotent** — processing the same event twice must be harmless.

The policy-data payload has no dedicated event-id field, so de-duplicate on a **hash of the payload contents** — identical content means it's the same event. Record hashes you've already handled and skip repeats:

```js
// server (sketch)
const crypto = require("crypto");
function handle(event) {
  const key = crypto.createHash("sha256")
    .update(JSON.stringify(event.data)).digest("hex");   // content fingerprint
  if (alreadyProcessed(key)) return;      // skip duplicate
  markProcessed(key);
  // …save the policy data…
}
```

See Delivery & de-duplication for patterns. (The form-submission event is simpler — it carries a `submission_id` you can key on directly.)

## Step 8 — Try it out (sandbox)

1. In the **API section**, use **Send test event** to fire a sample `client-submission` at your staging URL.
2. Watch your server log the topic and the payload.
3. Check the **delivery log** in the dashboard — you'll see the attempt and your `200` response.

If nothing arrives, jump to Troubleshooting → "My webhook isn't arriving".

## Step 9 — Go live

When staging works end to end:

1. Deploy your endpoint to a real HTTPS URL.
2. Add that URL as your **production** `client-submission` webhook.
3. Switch to your **production** signing secret and API token.
4. Run the Integration checklist.

***

## Next steps

* [Embed a form to collect submissions →](/intake-forms/b-embed-form.md)
* Webhook events & payloads (all fields) →
* Verify signatures — deeper dive →


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.insurgrid.com/quickstarts/a-receive-policy-data.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
