> 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/guides/delivery-dedup.md).

# Delivery & de-duplication

InsurGrid's delivery is **at-least-once**, not exactly-once. That single fact shapes how you should write every webhook handler: it must be safe to run twice on the same event.

> **New term — at-least-once delivery:** a guarantee that an event will be delivered one time *or more* — never zero times — but occasionally more than once. The tradeoff for "we'll keep trying until it lands" is that your handler has to tolerate the same event arriving twice.

## Why duplicates happen

A duplicate isn't a bug on InsurGrid's side — it's the normal cost of guaranteeing nothing gets silently dropped. A common case: your endpoint finishes processing and is about to respond, but the response is slow to arrive or gets lost in transit. From InsurGrid's side, that looks identical to a failure, so the event is retried — even though you already handled it.

## Retries, backoff, and replay

If your endpoint doesn't return a `2xx` status, InsurGrid retries the delivery automatically, spacing attempts out over time rather than firing them all at once. Every attempt — successful or not — is recorded in a **delivery log** you can review in the **API section**, and a failed delivery can be **replayed** on demand once your endpoint is fixed.

In plain terms: you don't need to build your own retry or delivery-tracking system. InsurGrid keeps trying on your behalf and gives you visibility into what was sent and when. What you're responsible for is making sure a repeated delivery doesn't cause a repeated *effect* on your side.

## Make your handler idempotent

> **New term — idempotent:** an operation that produces the same end result no matter how many times it runs. Processing the same webhook once or three times should leave your system in the same state either way.

The standard pattern: derive a stable **de-dupe key** for the event, record every key you've successfully processed, and skip anything you've already seen. Which key to use depends on the event (see the next section).

```js
// server (sketch)
const crypto = require("crypto");
function dedupeKey(event) {
  // form-submission carries its own id; policy-data uses a content hash
  return event.data.submission_id
    ?? crypto.createHash("sha256").update(JSON.stringify(event.data)).digest("hex");
}
function handle(event) {
  const key = dedupeKey(event);
  if (alreadyProcessed(key)) return;   // skip duplicate
  markProcessed(key);
  // …save the data, trigger downstream work…
}
```

```python
# server (sketch)
import hashlib, json
def dedupe_key(event):
    # form-submission carries its own id; policy-data uses a content hash
    sid = event["data"].get("submission_id")
    if sid:
        return str(sid)
    return hashlib.sha256(json.dumps(event["data"], sort_keys=True).encode()).hexdigest()

def handle(event):
    key = dedupe_key(event)
    if already_processed(key):
        return  # skip duplicate
    mark_processed(key)
    # …save the data, trigger downstream work…
```

`alreadyProcessed` / `already_processed` can be as simple as a unique constraint on the id column in your database — an insert that violates the constraint *is* your duplicate check.

## What id to de-dupe on

| Event                                    | De-dupe on                                                                                                                                                           |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Form submissions** (`form-submission`) | The `submission_id` in the payload body — it identifies the underlying submission no matter how many times it's delivered. Use this directly.                        |
| **Policy data** (`client-submission`)    | The payload has no dedicated id field. De-dupe on a **hash of the payload contents** (`data`): compute a SHA-256 of the JSON and treat a repeat hash as a duplicate. |

> If your endpoint receives a delivery-id header from your webhook setup, you can use that instead — confirm the exact header for your account during onboarding. When in doubt, the content hash above always works.

## Ordering is not guaranteed

Don't assume events arrive in the order they happened. If you subscribe to multiple topics for related data (for example, a submission event and a per-file upload event), one may arrive before the other. Design your handler to tolerate out-of-order arrival — for example, upsert by id rather than assuming "this is the first thing I've seen for this record."

## Related

* [Idempotency keys](/guides/idempotency.md) — the equivalent safety net for the *writes you send to* InsurGrid.
* [Verify a webhook signature](/guides/signing-verification.md)
* [Register your endpoint & choose an auth type](/guides/register-endpoint-auth-types.md)
* [Quickstart A — Receive policy data](/quickstarts/a-receive-policy-data.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/guides/delivery-dedup.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.
