Collection webhooks

Get notified the moment a collection reaches a final state, instead of polling the status endpoint. A webhook is a lightweight signal — it tells you which collection finished and how, so you can fetch the result from the Insurely API.

How it works

You register an HTTPS endpoint with Insurely and receive a signing secret.

When one of your collections reaches a terminal status, we POST a small JSON event to your endpoint, signed with your secret.

You verify the signature, respond 2xx quickly, and then call GET /collections/{collectionId}/status (and the data endpoints) to retrieve the result.

Webhooks complement the /status endpoint — they don't replace it. Treat the webhook as a "go fetch" signal; /status remains the source of truth.

The payload

Every delivery is a JSON body with these fields:

FieldTypeDescription
collectionIdstringThe collection that reached a terminal status. Use it to fetch via the API.
statusstringThe terminal status (see the table below).
eventIdstring (UUID)Unique id for this event. Use it to de-duplicate (see Idempotency).
timestampstring (ISO-8601)When the event was generated.
Delivery
POST https://your-endpoint.example.com/insurely-webhook
Content-Type: application/json
X-Insurely-Event-Id: 6f1c2e9a-3b1d-4a77-9b2e-2c0a8f3d4e55
X-Insurely-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

{
  "collectionId": "c0ffee00-1234-5678-9abc-def012345678",
  "status": "COMPLETED",
  "eventId": "6f1c2e9a-3b1d-4a77-9b2e-2c0a8f3d4e55",
  "timestamp": "2026-06-11T09:21:44.512Z"
}

The payload deliberately carries no collected data or PII — fetch the collection from the Insurely API after you receive the event.

Headers

HeaderDescription
X-Insurely-SignatureLowercase-hex HMAC-SHA256 of the exact request body, keyed by your signing secret. Verify this.
X-Insurely-Event-IdSame value as eventId in the body; your idempotency key.
Content-Typeapplication/json

Status values

A webhook is sent for every terminal status — i.e. any state where the collection will receive no further updates: success, failure, and states that need end-user action. The status field is one of:

GroupValues
CompletedCOMPLETED, COMPLETED_PARTIAL, COMPLETED_EMPTY
FailedFAILED, FAILED_PDF_PARSE, FAILED_PDF_USER_INPUT, THIRD_PARTY_ERROR
Action neededINCORRECT_CREDENTIALS, AUTHENTICATION_TIMEOUT, AUTHENTICATION_CANCELLED, AUTHENTICATION_CONFLICT, AUTHENTICATION_MISMATCH, TWO_FACTOR_METHOD_SELECTION_TIMEOUT, KYC_FORM, CONTACT_FORM, CUSTOMER_ENROLLMENT_REQUIRED, COLLECTION_INPUT_TIMEOUT

Treat status as a stable, webhook-specific vocabulary. For the authoritative, fully-detailed status, call /status. New values may be added over time — handle unknown values gracefully.

Verifying the signature

Compute the HMAC-SHA256 of the raw request body bytes using your signing secret, render it as lowercase hex, and compare it (in constant time) to the X-Insurely-Signature header. Reject the request if it doesn't match.

Node.js
const crypto = require("crypto");

function isValid(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")   // the EXACT received bytes — do not re-serialize
    .digest("hex");
  const a = Buffer.from(signatureHeader || "", "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hashlib, hmac

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

Verify against the raw body your framework received, not a re-serialized object — JSON re-encoding can change bytes (key order, whitespace) and break the signature.

Idempotency

Delivery is at-least-once: you may receive the same event more than once (e.g. if your endpoint is slow to acknowledge and we retry). De-duplicate on X-Insurely-Event-Id — process each id once.

A single collection can emit more than one terminal webhook over its lifecycle (for example COMPLETED_PARTIAL then COMPLETED). Each is a distinct event with its own eventId, so don't collapse different events by collectionId.

Retries & failures

  • Respond with any 2xx to acknowledge. Anything else — non-2xx, a timeout, or a connection error — is treated as a failed delivery.
  • Failed deliveries are retried with exponential backoff over several attempts. After they're exhausted we stop retrying that event; you can still reconcile via /status.
  • We do not follow redirects, and we apply request timeouts — point the webhook directly at your final HTTPS endpoint.
  • Acknowledge fast (do the heavy work asynchronously). A slow endpoint looks like a failure and gets retried.

Requirements & best practices

  • Endpoint must be HTTPS and publicly reachable (no internal/private addresses).
  • Always verify the signature before trusting an event.
  • De-duplicate on eventId.
  • Use the webhook as a trigger to fetch; rely on /status as the source of truth.
  • Keep your signing secret secret. Contact Insurely to rotate it if exposed.

Last updated on