Skip to main content

What are Webhooks?

Webhooks allow you to receive automated notifications when important compliance events occur for your downstream entities and agents. Instead of continuously polling our API for updates, we’ll automatically send HTTP POST requests to your specified endpoint whenever license statuses change or NIPR data is updated.
Webhooks belong to the Carrier & MGA API. Every event here fires about something your organization owns or oversees: an agency you appoint, a producer at one, or one of your own surplus-lines filings. Only a credential on that surface (carrier, MGA or wholesaler) can register a subscription.There is no agency equivalent yet: an agency cannot subscribe to events about its own licences and appointments. If that would be useful for your integration, tell us at support@turrisfi.com — until then, poll Actions Required or the licensing summaries on a schedule.

Quick Start

1

Set up your endpoint

Create an endpoint to receive POST requests
2

Register your webhook URL

Register your webhook URL using our API
3

Verify signatures

Verify webhook signatures using your client secret
4

Handle events

Process events based on the payload data
5

Test integration

Test your integration using our test webhook feature

Available Webhook Events

Agent Compliance Status Change

Triggered when an agent’s compliance status changes due to license, appointment, or requirement updates.

Agency Compliance Status Change

Triggered when a downstream entity’s (agency) compliance status changes due to license, appointment, or requirement updates.

Agency Created

Triggered when an agency relationship is created in your account, with the hierarchy needed to tell whether it is covered by a parent’s producer agreement.

Agency Deleted

Triggered when an agency relationship is removed or archived.

Agency Updated

Triggered when an agency’s name, identity, or contact details change, with the fields that changed and their previous values.

Compliance Data Synchronized

Triggered when NIPR data synchronization completes for an agent or downstream entity.

Producer Agreement Executed

Triggered when a downstream entity and your upstream entity have fully executed a producer agreement.

Surplus Lines Filing Status Change

Triggered when a surplus lines filing’s status changes (e.g., from In Progress to Filed).

Surplus Lines Filing Action Required

Triggered when a surplus lines filing checklist item escalation newly requires customer action.

Agent Regulatory Action Added

Triggered when NIPR reports a regulatory action against one of your producers that Turris has not seen before.

Agency Regulatory Action Added

Triggered when NIPR reports a regulatory action against one of your agencies that Turris has not seen before.

Agent Regulatory Action Updated

Triggered when NIPR amends a regulatory action already on file against one of your producers, for example a disposition escalating after the original filing.

Agency Regulatory Action Updated

Triggered when NIPR amends a regulatory action already on file against one of your agencies.

Agent License Expiring

Triggered 60 days before a producer license reaches its renewal date.

Agency License Expiring

Triggered 60 days before an agency license reaches its renewal date.

Agent License Expired

Triggered the day after a producer license passes its renewal date.

Agency License Expired

Triggered the day after an agency license passes its renewal date.

Planned Webhooks

We plan to build many more webhook events, including:
  • Downstream entity/agent authority status
  • E&O compliance status changes
  • E&O/Cyber policy renewals

Payload Envelope

All webhook payloads follow this general structure:
Deliveries triggered from the Turris Web App’s Send test event feature include an additional top-level "test": true field in this envelope. Real (production) events never include it, so you can safely route or ignore test deliveries in your receiver.

Setting Up Your Webhook Endpoint

Your webhook endpoint must:
Accept POST requests with JSON payloads
Respond with 2xx status codes (200-299) for successful processing
Respond within 30 seconds to avoid timeout
Handle duplicate events gracefully (use payload content for deduplication)
Verify the X-Turris-Signature header using your client secret

Example Endpoint Response

Webhook Security

All webhook requests are signed using HMAC-SHA256 with your client secret. This allows you to verify that the request originated from Turris and that the payload has not been tampered with. Webhook requests include the following headers: The request body is sent as plaintext JSON — no decryption is required.
Your client secret is the only way to verify webhook authenticity. Store it securely as an environment variable — never hard-code it in your application.

Verifying Webhook Signatures

Verify the signature on every request. This is a required step, not a hardening measure.Your endpoint is a public URL and the body arrives as plaintext, so the signature is the only thing that distinguishes a real Turris event from anything else posted to that URL. A receiver that parses the body without checking the signature will act on whatever it is sent, by anyone who learns the address.
Recompute the HMAC-SHA256 signature, compare it to the X-Turris-Signature header, and reject the request unless both the signature matches and the timestamp is recent.

How the Signature is Computed

The signature is computed over the string {timestamp}.{payload}, where:
  • {timestamp} is the value of the X-Turris-Timestamp header
  • {payload} is the raw JSON request body (as a string)
Including the timestamp inside the signed string means the timestamp cannot be altered without invalidating the signature. On its own that does not stop a replay: an attacker who captures a valid request can send those exact bytes again, and they will still verify. What stops it is rejecting stale timestamps, which is why the window check below is part of verification rather than an optional extra.

Verification Example (Node.js)

Usage Example

Three details in the example above are the ones integrations usually get wrong:
  1. Sign {timestamp}.{rawBody}, not the body alone.
  2. Use the raw bytes, captured before JSON.parse. Re-serialising the parsed object changes key order and whitespace, so the signature will not match even though nothing was tampered with.
  3. Compare with a timing-safe comparison, and check the buffer lengths first, because timingSafeEqual throws when they differ.
A ready-to-run reference implementation lives in the Turris repository at packages/tooling/scripts/crypto/verify-webhook.mjs.
Your client secret is provided when you register a webhook. Store it securely as an environment variable — never hard-code it in your application.

Event Debouncing & Batching

Compliance status change webhooks use a sliding-window debounce to prevent webhook floods during bulk operations: How it works:
  1. A compliance-relevant change is detected (e.g., license created, appointment updated)
  2. Turris starts a 1-minute timer for the affected upstream entity
  3. If another change occurs within that minute, the timer resets
  4. When 1 minute passes with no new changes (or the 30-minute cap is reached), all accumulated changes are evaluated
  5. A single webhook is sent containing only the entity/product/state combinations where the compliance status actually changed
Compliance Data Synchronized (ENTITY_COMPLIANCE_DATA_SYNCHRONIZED), Producer Agreement Executed (PRODUCER_AGREEMENT_EXECUTED), Agency Created (DOWNSTREAM_ENTITY_CREATED), Agency Deleted (DOWNSTREAM_ENTITY_DELETED), Agency Updated (DOWNSTREAM_ENTITY_UPDATED), AML / OFAC Check Status Change (AML_OFAC_CHECK_STATUS_CHANGE), Surplus Lines Filing Status Change (SURPLUS_LINES_FILING_STATUS_CHANGE), Surplus Lines Filing Action Required (SURPLUS_LINES_FILING_ACTION_REQUIRED), Agent Regulatory Action Added (AGENT_REGULATORY_ACTION_ADDED), and Agency Regulatory Action Added (DOWNSTREAM_ENTITY_REGULATORY_ACTION_ADDED) webhooks are sent immediately — they are not debounced, since each is a discrete event requiring timely customer action.
The four licence-expiry events are neither debounced nor immediate. Agent License Expiring, Agency License Expiring, Agent License Expired and Agency License Expired are driven by a daily sweep at 05:00 America/New_York, because they fire on a date being crossed rather than on a record changing. There is nothing to react to at the moment it happens.One delivery per licence per boundary, no batching. Each licence matches on exactly one calendar day, so you get one delivery per licence per renewal cycle. If a sweep does not run, that day’s licences are not announced later: there is no catch-up pass.If you subscribe to more than one of the four, size for the combined sweep rather than the largest event. A typical sweep is single digits and the 95th percentile is under 50, but the busiest measured sweep for a single carrier carries about 8,000 deliveries across all four — noticeably more than the ~2,800 worst case of any one event.The reason is that the two boundaries are 61 days apart, not 60, so their spikes do not stay separated. Renewal dates cluster on the last day of the month. An expired spike therefore lands on the 1st, and an expiring spike lands 60 days before a month end, which — because 60 days is not two calendar months — also falls on the 1st for some month pairs. When both coincide, one sweep carries a whole month-end expired cohort and a whole expiring cohort at once.
Because Agency Created is not debounced, a CSV bulk upload produces one delivery per agency in the file. A large import therefore produces a burst proportional to its size. Size your receiver to absorb or queue it.Agency Updated has a larger burst surface than Agency Created, for two reasons that multiply: a change to an agency record is delivered once per relationship you hold with that agency, and the daily NIPR sync can rewrite many agency names in a single pass. See Agency Updated for the detail.

Batched Payloads

Because changes are debounced, a single webhook delivery may contain multiple status changes in the payload array. Each element represents a distinct entity + product + state combination that changed. Design your handler to iterate over the full array.

Reliable Delivery

Automatic Retries

If your endpoint is unavailable or returns an error, we’ll automatically retry delivery using exponential backoff: We’ll attempt delivery up to 3 times over several hours.

Manual Resend

You can manually resend any webhook event through the Turris Web App. This will cancel any pending automatic retries to prevent duplicates.

Testing Your Integration

You can test your webhook integration in the following ways:
  1. Self-test over the API (recommended for automation): Trigger a real, signed test delivery straight from your own code, no web app needed. First call List Webhooks to find the id of the webhook you want to exercise, then call Send Test Event with that id and an optional scenario. Turris enqueues a real HMAC-signed POST of a realistic sample payload to that webhook’s registered URL, shaped exactly like a production event. Omit scenario to use the type’s default; pass a scenario key to exercise a specific case (for example a surplus lines filing status, or a compliant vs non-compliant result). This is ideal for wiring webhook checks into your CI or integration tests.
  2. Send a test event from the web app: In the Turris Web App, open Settings > Webhooks, use the actions menu on any registered webhook, and choose Send test event. Pick a scenario and trigger it. The delivery is identical to the API self-test above.
  3. Sandbox environment: Configure webhooks with your development endpoint and trigger compliance changes in a sandbox environment to receive real webhook payloads. See the caveat below — this does not work for the NIPR-backed events.
  4. Manual resend: Use the Turris Web App to resend any previously delivered webhook event to your endpoint.
Test deliveries carry "test": true in the envelope and are flagged as test events in the delivery history, so they never mix with your real events. This helps you verify reachability, signature verification, and payload parsing before any real activity exists.
Sandbox cannot exercise the NIPR-backed events, so use the test send for those. Licence, appointment, regulatory-action, address and NIPR-sourced contact data in sandbox is sparse, and where it exists it is often inaccurate or years out of date. So for the four licence-expiry events and the four regulatory-action events, “configure your endpoint in sandbox and wait for a change” produces nothing, and the silence looks identical to a broken integration.Verify those with Send Test Event (option 1 or 2), which works in every environment and proves the three things that are actually yours to get right: reachability, signature verification and payload parsing. Turris verifies the trigger logic itself.
For the regulatory-action events, test both scenarios, not just the default. Pass scenario explicitly:
  • criminal — every field populated.
  • administrative — all seven nullable NIPR fields arrive as null.
A receiver written against the first will often break on the second, and administrative actions are the more common of the two in practice. The same applies to the entity fields on all six new events, which resolve in two independent tiers and can arrive partially: see each event’s payload reference.

Monitoring & Troubleshooting

Delivery History

In our web app you can view the status of all webhook deliveries, including:
  • Success/failure status
  • Response times
  • Number of retry attempts
  • Error details

Common Issues

Best Practices

Idempotency

Use payload content (entity ID, state, product) to detect and handle duplicate deliveries gracefully.

Logging

Log all incoming webhook events for debugging and audit purposes.

Async Processing

Respond quickly (2xx status) then process the event asynchronously to avoid timeouts.

Security

Store your client secret securely and consider IP whitelisting for additional security.