Policy Intake
Create an employer and a direct-billing policy in a single call, then immediately create a payment link to collect premium. This is the fast path for partners onboarding a brand-new insured end to end — no pre-provisioning of records in Audit1 required.
Create an Employer & Policy #
POST /api/v1/intake/policies
Authenticate with your X-Client-ID / X-Client-Secret (requires a payments- or all-scoped key). One atomic call creates an employer and a direct billing policy so you can create a payment link for the new policy right away. The carrier is inferred automatically for single-carrier keys; multi-carrier keys must pass carrier_id, and it must be within your key's scope.
Request #
Top-level fields:
| Field | Type | Required | Description |
|---|---|---|---|
idempotency_key |
string | Yes | Re-submitting with the same key returns the original ids — never a duplicate |
employer |
object | Yes | The employer to create or resume (see Employer below) |
owners |
array | No | Business owners / officers (see Owners below) |
policy |
object | Yes | The direct-billing policy to create (see Policy below) |
carrier_id |
string | Conditional | Carrier ObjectId. Required unless your API key is scoped to exactly one carrier — single-carrier keys infer it server-side. Must be within your key's scope |
Idempotency & FEIN dedupe
— resubmitting the same idempotency_key returns the original ids. Dedupe also applies on the employer's FEIN within your tenant: resubmitting the same FEIN resumes and returns the same employer instead of creating a duplicate. A FEIN that already exists outside your tenant returns 409 fein_conflict.
Employer
| Field | Type | Required | Description |
|---|---|---|---|
legal_name |
string | Yes | Registered legal business name |
fein |
string | Yes | 9-digit FEIN. An SSN/ITIN is accepted for sole proprietors; normalized to XX-XXXXXXX |
entity_type |
string | No | Legal structure (e.g., LLC, Corporation) |
address |
object | Yes | Business address. line1, city, state, and zip are all required |
contact_name |
string | No | Primary contact name |
contact_email |
string | No | Primary contact email |
phone |
string | No | Contact phone |
email |
string | No | Employer email |
Owners
Each entry in owners[] describes an owner or officer. Optional, but recommended for accurate rating.
| Field | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | Owner first name |
last_name |
string | Yes | Owner last name |
ownership_percentage |
number | No | Ownership stake, 0–100 |
excluded |
boolean | No | true excludes the officer from coverage. Defaults to false (officer included) |
class_code |
string | No | NCCI class code for the owner |
dob |
string | No | Date of birth — recorded for audit purposes |
title |
string | No | Officer title — recorded for audit purposes |
Policy
| Field | Type | Required | Description |
|---|---|---|---|
state |
string | Yes | 2-letter state code where coverage applies |
class_code |
string | Yes | Primary NCCI class code |
effective_date |
string | Yes | ISO date the policy takes effect. Expiration is set automatically to +1 year |
annual_premium_cents |
integer | Yes | Annual premium in cents — positive integer ($1,000.00 = 100000) |
policy_number |
string | No | Your policy number. Auto-assigned if omitted |
el_limit_tier |
string | No | Employers-liability limit tier — recorded for audit purposes |
Response #
201 Created — or 200 OK with the same body on an idempotent replay:
{
"employer_id": "681xyz789abc123456789012",
"policy_id": "682def789ghi012345678901"
}
The policy is created in the in_progress state. It flips to active automatically when its payment link completes — at the same moment the payment.completed webhook fires.
Errors #
| Status | Code | Meaning |
|---|---|---|
400 |
validation error | A required field is missing or invalid, or carrier_id is missing on a multi-carrier key |
403 |
out-of-scope carrier | The carrier_id is not within your API key's scope |
409 |
fein_conflict |
The FEIN already exists under a different tenant |
502 |
partial failure | Downstream creation partially failed. The response includes an intake_id; retry with the same idempotency_key to resume |
Example #
Create the employer and policy:
curl -X POST /api/v1/intake/policies \
-H "X-Client-ID: $CLIENT_ID" \
-H "X-Client-Secret: $CLIENT_SECRET" \
-d '{
"idempotency_key": "intake-smith-2026-05-01-001",
"employer": {
"legal_name": "Smith Industries LLC",
"fein": "123456789",
"entity_type": "LLC",
"address": {
"line1": "500 Industrial Way",
"city": "Phoenix",
"state": "AZ",
"zip": "85001"
},
"contact_name": "Dana Smith",
"contact_email": "ap@smithindustries.com"
},
"owners": [
{
"first_name": "Dana",
"last_name": "Smith",
"ownership_percentage": 100,
"excluded": false,
"class_code": "8810"
}
],
"policy": {
"state": "AZ",
"class_code": "5183",
"effective_date": "2026-05-01",
"annual_premium_cents": 100000,
"policy_number": "WC-2026-0042"
}
}'
Response (201 Created):
{
"employer_id": "681xyz789abc123456789012",
"policy_id": "682def789ghi012345678901"
}
Then create a pay-in-full link for the returned policy — no carrier_id needed on a single-carrier key:
curl -X POST /api/v1/payment-links \
-H "X-Client-ID: $CLIENT_ID" \
-H "X-Client-Secret: $CLIENT_SECRET" \
-d '{
"employer_id": "681xyz789abc123456789012",
"policy_id": "682def789ghi012345678901",
"payment_type": "ONE_TIME",
"payment_context": "full_payment",
"premium_cents": 100000,
"customer": {
"name": "Smith Industries LLC",
"email": "ap@smithindustries.com"
},
"after_completion_url": "https://yourplatform.com/thanks"
}'
The employer_id and policy_id come from the intake response. See Payment Links & Subscriptions for the full payment-link field set and the full_payment payment context.
Sandbox #
Test keys (audit1_test_*) run the identical intake flow with zero Stripe traffic — no real customers, charges, or emails. Use them to build and verify your integration end to end.
Sandbox payment links can be completed programmatically:
POST /api/v1/payment-links/{id}/simulate-completion
Available on sandbox keys only — live keys receive 403. Calling it marks the sandbox payment link as paid and fires the same payment.completed webhook to your sandbox webhook subscriptions, so you can exercise your completion handler without a real payment.
Sandbox vs live
— intake records created with a live key are real production records: a real employer and a real policy. Use audit1_test_* keys for all integration testing.
Webhook: payment.completed #
When the payment link for an intake policy completes, Audit1 fires a payment.completed webhook to every matching subscription (sandbox links fire it to your sandbox subscriptions). The policy flips to active at the same time.
The delivered request body is the event's data object — there is no envelope wrapper. Event metadata travels in the headers:
| Header | Value |
|---|---|
X-Webhook-Signature |
HMAC-SHA256 signature, hex-encoded |
X-Webhook-Timestamp |
Unix timestamp in milliseconds |
X-Webhook-Event |
payment.completed |
X-Webhook-ID |
Unique event ID |
Example body:
{
"payment_id": "686abc123def456789012345",
"payment_link_id": "686def456abc789012345678",
"stripe_payment_link_id": "plink_1PXYZabcdef",
"policy_id": "682def789ghi012345678901",
"employer_id": "681xyz789abc123456789012",
"carrier_id": "680abc456def789012345678",
"amount_cents": 100000,
"premium_cents": 100000,
"fee_cents": 0,
"payment_type": "ONE_TIME",
"payment_context": "full_payment",
"paid_at": "2026-05-01T15:30:00.000Z"
}
amount_cents is the total charged (premium_cents + fee_cents).
Verifying the signature #
Audit1 signs each delivery with the subscription secret returned when you registered the webhook. Recompute and compare:
- Read
X-Webhook-SignatureandX-Webhook-Timestampfrom the headers. - Build the signed string
${timestamp}.${raw_body}— the raw request body, exactly as received. - Compute
HMAC-SHA256with your webhooksecretand hex-encode it. - Compare against
X-Webhook-Signatureusing a constant-time comparison.
See Payment Webhooks to register an endpoint, view every event type, and get language-specific verification snippets, the retry schedule (1s / 5s / 25s / 125s / 625s), and the auto-disable policy.
All monetary values are in cents. $1,000.00 =
100000. This avoids floating-point precision issues.