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.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
One Webhook, Many Events
A webhook is one URL plus the list of event types it receives. Register the URL once, select every event you want it to get, and you hold one subscription with one signing secret. That is what makes signature verification tractable. Because each webhook subscription has exactly one secret, and one URL is one subscription, the request that arrives can only have been signed with it — there is no key to choose. Every delivery also carriesX-Turris-Webhook-Id
naming the subscription, so a receiver serving more than one Turris webhook can select the right secret from a header
rather than by parsing the body it has not verified yet.
Subscriptions are still per event type. Selecting Agency Created does not deliver Agency Updated; select both if you
want both. What you no longer do is register the same URL twice.
The webhook resource carries
types (an array), not type. Before September 2026 it was a single value and one URL
needed one registration per event type, each with its own secret. See the
changelog for the migration note.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 secretExample 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.
X-Turris-Webhook-Id matches the id from List Webhooks. Use it to look
up which secret to verify against when your receiver serves more than one Turris webhook. It is outside the signed
body on purpose: a value used to select the verification key cannot come from the content that key is verifying.
Verifying Webhook Signatures
Recompute the HMAC-SHA256 signature, compare it to theX-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 theX-Turris-Timestampheader{payload}is the raw JSON request body (as a string)
Verification Example (Node.js)
Usage Example
Three details in the example above are the ones integrations usually get wrong:
- Sign
{timestamp}.{rawBody}, not the body alone. - 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. - Compare with a timing-safe comparison, and check the buffer lengths first, because
timingSafeEqualthrows when they differ.
packages/tooling/scripts/crypto/verify-webhook.mjs.Event Debouncing & Batching
Compliance status change webhooks use a sliding-window debounce to prevent webhook floods during bulk operations:
How it works:
- A compliance-relevant change is detected (e.g., license created, appointment updated)
- Turris starts a 1-minute timer for the affected upstream entity
- If another change occurs within that minute, the timer resets
- When 1 minute passes with no new changes (or the 30-minute cap is reached), all accumulated changes are evaluated
- 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.
Batched Payloads
Because changes are debounced, a single webhook delivery may contain multiple status changes in thepayload 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 returns a non-2xx status, takes longer than 20 seconds to respond, or cannot be reached, we retry the delivery automatically with exponential backoff starting at 15 seconds. Each live event gets five attempts in total:
When your endpoint fails fast, the five attempts span about four minutes; an attempt that times out adds up to 20 seconds. If the fifth attempt also fails, we stop retrying and the event stays failed in your delivery history until you resend it.
Manual Resend
You can resend failed events from Settings > Webhooks in the Turris Web App, one at a time from an event’s actions menu, or in bulk by selecting failed events and choosing Resend. Events that were already delivered cannot be resent. A manual resend is a single attempt. If it fails, it is recorded as another failed attempt and is not retried automatically, so you can resend again once your endpoint is fixed. A successful resend cancels any automatic retries still pending for that event, so we do not deliver it twice. Live deliveries are sent ahead of queued resends, so a large bulk resend does not hold up new events. A resend already in flight can delay a live delivery by up to 20 seconds. The web app shows the progress of a bulk resend while you stay on the events view. If you leave it, the progress banner disappears, and you can follow the batch on the Bulk Requests page.Testing Your Integration
You can test your webhook integration in the following ways:- 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
idof the webhook you want to exercise, then call Send Test Event with thatidand an optionalscenario. Turris enqueues a real HMAC-signed POST of a realistic sample payload to that webhook’s registered URL, shaped exactly like a production event. Omitscenarioto 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. - 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.
- 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.
- Manual resend: Use the Turris Web App to resend a failed webhook event to your endpoint, one at a time or in bulk from Settings > Webhooks.
"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.
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 asnull.
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.