GUIDES

Implementation guide

Build the full integration in eight steps. You do the first four once per account or per customer. After that your system pushes documents and reacts to events. If the words consumer, engagement, or surface check are new, read How it works first.

  1. Map your ids to ours

    Decide which of your records is the insured and which is the consumer before you write code. Use ids your system already owns and does not reuse.

    Yours Ours Rule
    Whoever's rules apply: your customer, or you consumer_ref One per customer whose rules differ. Use your own name when the rules are yours
    The vendor, contractor, or company you onboard insured_ref Stable for the life of the record. Do not use an email address
    A parent company the insured works under parent_insured_ref Lets a parent's line satisfy a child's rule
    Your own document id or filename document_ref Optional, echoed on every event

    Store our ids next to your rows: the document id from the push receipt, plus coverage_line_id, request_id, and verdict_id as they appear. You need them to read state and to answer support questions.

  2. Create the consumer

    PUT /consumers/{consumer_ref} with a name, contacts, and any metadata you want echoed back. It is an upsert. Call it from your customer provisioning flow.

    A consumer has no effect on results until a requirement set exists for it.

  3. Publish the requirement set

    Turn the customer's insurance SOP into rules with POST /consumers/{consumer_ref}/requirement-sets. A consumer usually carries one requirement set (key: default). Add a different key only when some insureds follow different rules from the rest. Each rule names a coverage type, the requirement, the class of document that is acceptable, and who must confirm it. Publish a first version with applies_to: new. For a later change, call preview, then publish all_active with a grace_period_days window, 14 by default. Publishing all_active rebinds only the engagements following that key.

    Send structured rules when you can. A PDF or DOCX source_document is parsed into a draft for you to review.

  4. Point delivery at your endpoint

    PUT /delivery with a webhook url and the events you want, or ["*"]. The response returns the signing secret once. Store it. Add a batch block as well if a scheduled file suits your data team better than a stream.

    Each key's mode has its own delivery configuration. Configure it once with the test key and once with the live key.

  5. Push documents

    Wherever a certificate enters your system, call POST /documents with the file, insured_ref, and consumer_ref. The file field is files, repeated once per file. Push every version you receive. Insurance Verified deduplicates by hash and merges lines. You get 201 Created back with {transmission_id, idempotent_replay, engagement_id, engagement, documents[]}, and each document entry carries id, sha256, and content_bytes_deduplicated. Store the id and the engagement_id.

    Send an Idempotency-Key header on every POST and PUT, so a retry creates no second transmission. One push call is one transmission_id, and a replay returns that transmission.

    Send the file as you received it. Insurance Verified extracts and classifies it, and meta has no effect on what it reads.

  6. Handle events

    Verify the X-IV-Signature over <timestamp>.<body>, reject a delivery whose timestamp is more than five minutes old, then dispatch on X-IV-Event. The default subscription is document.* and verdict.*. Subscribe to the other families you handle.

    Event What to do in your system
    document.extracted Record the coverage lines against the insured. A line's request_id is null until its clock opens
    document.failed, document.not_insurance, document.unreadable_fields Ask the insured for a readable insurance document. reason or unreadable_fields[] says what went wrong
    verdict.issued Store it. kind: surface is the surface check, kind: verified is a verdict. scope: line is one coverage line resolving. scope: engagement is the roll-up, which sets the insured's status. On a failure, show the insured wording.summary and wording.next, and rules[] when they want detail
    verdict.superseded Mark the old verdict replaced and read the new one
    remediation.opened Ask the insured for the complete declarations packet named in ask
    insured_contact.missing Send the insured's email or phone with PUT /​insureds/​{insured_ref}
    request.sla_at_risk, request.overdue Surface it to your operations team, or add hours with POST /​requests/​{request_id}/​extend

    Store every event_id and drop duplicates. Deliveries retry for 24 hours and can arrive out of order.

  7. Read state when a screen needs it

    Events tell you when something changed. When a screen or a report needs the answer now, read it. When an event and a read disagree, use the read.

    Read What it gives you
    GET /​engagements/​{engagement_id} The whole verification in one object: the requirement set it follows, the surface check, the verdict, per-line verdicts, coverage lines, documents, and the event trail
    GET /​insureds/​{insured_ref}/​verdicts The verdict history against one consumer, {data, next_cursor}, newest first. consumer_ref is a required query parameter. engagement_id narrows to one engagement when the insured holds several with the consumer
    GET /​verdicts/​{verdict_id} One verdict, as a bare object with no data wrapper
    GET /​insureds/​{insured_ref}/​requests The clocks on one engagement, newest first. This read gives you a request_id with no webhook. consumer_ref is required
    GET /​requests/​{request_id} The clock on one line, with its attempts and extensions
    GET /​consumers/​{consumer_ref}/​engagements Every engagement under a consumer, newest first, filterable by requirement set, status, and insured
    GET /​documents/​{document_id} One document's status, what was extracted from it, and the engagement it fed
    GET /​insureds/​{insured_ref}/​coverage Every line on the insured and its verification state, including lines available from a parent

    A test-key engagement resolves in about a minute, so you can poll GET /engagements/{engagement_id} end to end.

  8. Test, then go live

    Run the whole flow on a test key. Outreach is simulated, verdicts carry test: true, and the lifecycle resolves in about a minute. POST /delivery/webhook/test sends a signed ping to your endpoint. Swap in the live key when your handlers pass.

    Test keys are prefixed ive_test_ and live keys ive_live_. You can hold both at once. A live key reads no test data, and a test key reads no live data.

The consumer holds no key and makes no calls. To show your customer its verdicts, build a screen from your copy of the events, or send it the batch file.

What to keep on your side

Keep Why
The webhook secret and the key Each is shown once
Every event_id you have processed Deduplication
The transmission_id and document id from every push receipt Tying your file to ours in a support question
The engagement_id per insured, consumer, and requirement set The id to poll GET /​engagements/​{engagement_id} with
The latest engagement-scope verdict_id per insured and consumer, with its status and kind Your status screen
coverage_line_id per line Insurance Verified found Support and audit questions
request_id per open line Answering "where is it" without a read

Reading rules[]

The field that carries the outcome depends on the verdict's kind. Here is the same requirement at both stages.

On kind: surface, the paper reading is in surface. result is unverified because the broker has not answered yet.

{ "rule_key": "general_liability.limit_min.each_occurrence",
  "surface": "pass", "result": "unverified", "severity": "hard",
  "reasons": ["verification_pending"], "flags": [],
  "required": {"amount": 1000000}, "observed": {"amount": 1000000},
  "verification_required": "broker", "verification_observed": null }

On kind: verified, the rule carries no surface field, and result carries the outcome.

{ "rule_key": "general_liability.limit_min.each_occurrence",
  "result": "pass", "severity": "hard",
  "verification_state": "verified", "reasons": [], "flags": [],
  "required": {"amount": 1000000}, "observed": {"amount": 1000000},
  "verification_required": "broker", "verification_observed": "broker" }

Branch on rules[].surface at stage one and on rules[].result at stage two. status is the aggregate at both. A client that branches on result at stage one reads a passing surface check as a non-pass.

The full rule object:

Field What it is
rule_key <coverage_type>.<requirement>.<label>, for example general_liability.limit_min.each_occurrence. An any_of rule carries the key you chose
result pass, fail, gap, unverified, not_applicable. Branch on it when kind is verified
surface pass, fail, not_yet, pending_review, not_applicable. Present when kind is surface. Branch on it there
stage surface, open, verified. Present on the engagement roll-up only, saying how far this requirement got
verification_state The line's outcome at the source. Present on a kind: verified verdict
severity hard blocks the engagement, soft warns
required, observed The value the rule asked for and the value found
reasons[] Why it reads that way: verification_pending, limit_below_minimum, document_class_below_required, policy_form_unconfirmed, contradicted_at_source, verification_timed_out, and others. The list grows
flags[] Something a person may need to act on: timed_out, refused, contradicted, unable_to_verify, state_review_required
document_class_required, document_class_observed The paper the rule accepts and the paper received
verification_required, verification_observed Who had to confirm it and who did
coverage_line_id The line that decided the rule, or null when nothing was found
satisfied_by The alt_key of the any_of alternative that decided it
satisfied_via_parent, satisfied_via_program Set when a parent's line or an enrolled program decided the rule
alternatives[] Per-alternative results on an any_of rule
document_ids[], fact_ids[] What the reading was built from

Ignore reason codes and flags you do not know. New ones ship without a version bump.

Common mistakes

Mistake What to do
Branching on rules[].result on a surface check Read rules[].surface. On a surface check result is usually unverified with reason verification_pending
Showing the surface check as the verdict Show it as pending confirmation until a kind: verified verdict arrives
Branching on wording Branch on status and rules[]. wording changes without a version bump
Sending one document per insured Push every certificate, renewal, and endorsement you receive. Insurance Verified merges them into lines
Publishing a changed requirement set all_active without preview Call preview first. It lists who newly fails
Expecting a request_id on the push receipt Read it from document.extracted or GET /​insureds/​{insured_ref}/​requests once the clock opens
Expecting 202 and an array from POST /​documents The response is 201 with one object
Expecting 400 on a bad field Validation failures are 422, in the standard error envelope, with the field named in error.message
Reusing an insured_ref for a different party Mint a new ref. A ref stays bound to the party it first named

Go-live checklist

Check Done when
Consumer exists PUT /​consumers/​{consumer_ref} returns 200
Requirement set published GET /​consumers/​{consumer_ref}/​requirement-sets shows a published version
Webhook verified POST /​delivery/​webhook/​test lands and your signature check passes
Handlers idempotent Replaying a stored event changes nothing
Surface check and verdict both handled Your status screen distinguishes kind
Own domain verified, if you send from one Settings, then Sending, shows the domain as Verified
Live key swapped in The first verdict.issued arrives with test: false

Next