Skip to main content

Overview

Webhooks let your application be told when something happens in InstaView rather than polling for it. When a conversation completes, an analysis finishes, or another tracked event occurs, we POST it to your endpoint.

How Webhooks Work

1

Configure Endpoint

Register your webhook URL and specify which events to receive
2

Event Occurs

A conversation completes, an analysis finishes, or another tracked event happens
3

Receive Notification

InstaView sends an HTTP POST request to your endpoint with event details
4

Process & Respond

Your server processes the event and returns a 2xx status code

Event Types

InstaView supports the following webhook event types:
Which of the two names your endpoint receives is set per webhook — see Event name vocabulary. The other event families below are unaffected.
Conversation and analysis events cover every conversation in the company, not only the ones you created through this API. A conversation started from the InstaView dashboard emits the same conversation and analysis.* stream as one you created yourself — under whichever event names your webhook’s vocabulary selects. Filter on the conversationId values you know about if you only want your own.For an ATS integration, a webhook registered on the parent company also receives events for its child companies. Register one endpoint on the parent to cover every tenant, or one per child for separate endpoints — a child’s webhook only ever receives that child’s events, and never a sibling’s.If you register on both a parent and one of its children, that child’s events reach both endpoints as two separate deliveries with different deliveryIds. See Handling duplicates for the key that collapses them — deliveryId will not, by design.

Event name vocabulary

Conversation events answer to two names. InstaView’s API began in hiring, so its events were named for it; agents now run sales, support and operations calls, where interview.completed describes the wrong thing. Rather than rename the events and break every consumer switching on the string, each webhook carries the vocabulary its own events are delivered in.
Nothing you have registered changes. Every webhook that existed before this option was set to LEGACY, and stays there until you change it. There is no deadline and no migration to run. Only conversation events differ at all — analysis.*, sourcing.*, enrich.* and ping are identical in both vocabularies.
You receive exactly one of the two names, never both. Sending each event twice under two names would be worse than either name on its own: a consumer switching on the event string would process every call twice and never see an error. Switch when you are ready, with a PATCH on the webhook:
The switch applies to events triggered after it. A delivery already queued finishes under the name it started with, across every retry attempt, so no single trigger is ever delivered under two different names, and switching mid-flight cannot make a deduplicating consumer see one event as two. Switch back at any time; the same rule applies.
Registration accepts either spelling too. "CONVERSATION_COMPLETED" and "INTERVIEW_COMPLETED" register the same event and are stored identically, so you never have to write a noun you are migrating away from. What you receive is decided by vocabulary, not by the spelling you registered with — the two settings are independent.

Event Type Strings

There is a distinction between the strings used to register a webhook and the strings sent in the webhook payload:

Registration (Screaming Snake Case)

When creating or updating webhooks, use these uppercase strings in the events array. The five conversation events accept either spelling and store identically, so use whichever you prefer. Registering under a given spelling does not change what you receive: the delivered event name is decided by vocabulary alone. Registering the same event under both spellings subscribes you once, not twice. The rest have one spelling each, because those nouns never changed:
  • "ANALYSIS_COMPLETED" - Analysis finished successfully
  • "ANALYSIS_FAILED" - Analysis processing failed
  • "PING" - Test event for connectivity
  • "SOURCING_COMPLETED" - Sourcing run finished with scored candidates
  • "SOURCING_FAILED" - Sourcing run terminated with an error
  • "ENRICH_COMPLETED" - Enrichment run completed
  • "ENRICH_FAILED" - Enrichment run terminated with an error
Registering an event under both of its spellings subscribes you once, not twice — they are one event, so you receive one delivery for it.

Payload (Dot Notation)

The event field in the JSON payload you receive will use lowercase dot notation (e.g., "analysis.completed"). For conversation events, which of the two dot-notation names you get depends on the webhook’s vocabulary"conversation.completed" by default, "interview.completed" on a webhook registered before that option existed.

Payload Structure

Conversation events below are shown in the default NEUTRAL vocabulary. A webhook on LEGACY receives the same payloads under interview.started, interview.completed, interview.failed, interview.rescheduled and interview.cancelled instead. Only the event name differs — the payload body is identical in both. analysis.*, sourcing.*, enrich.* and ping are the same either way.
All webhook payloads follow this structure:

Conversation Events Payload

The id fields are sent under both names, always identical. conversationId and interviewId carry the same value, as do contactId and candidateId, so you can move field by field at your own pace rather than in one cut-over. conversationId and contactId are the current names; the other two are kept for the life of v1.Unlike the event label, the payload body does not depend on the webhook’s vocabulary — both spellings are always present, on every webhook. There is only ever one payload shape to handle.
runId attributes the call to its batch. It is null for a conversation created on its own, and carries the run’s id for one a run produced — a run mints one conversation per contact. It is always present, so you can switch on it directly without reading the conversation back.

Conversation Rescheduled Payload

Sent when a call attempt did not produce a completed conversation and a follow-up attempt has been scheduled. This event sits between two call attempts, so unlike the other conversation events it does not carry a single callAttemptId — the two attempts are split explicitly.
The contact’s line was busy, so the system scheduled another attempt on its own.
Field notes:

Conversation Cancelled Payload

Sent when a conversation reaches a terminal state without completing — either cancelled explicitly, or abandoned after exhausting its retry budget.
reason is one of: lastCallAttemptId is absent when the conversation was cancelled before any call was placed.
data.status is the event’s status, not the conversation’s. This payload always carries status: "CANCELLED", but for reason: "MAX_ATTEMPTS_EXCEEDED" the conversation itself now reads UNREACHABLE — fetching GET /conversations/{id} after this webhook returns a different value than the payload shows. Branch on reason, not on data.status, and re-read the resource if you mirror conversation status into your own system.
There is no API_REQUEST cancellation reason. The public API has no conversation-level cancel endpoint — DELETE /conversations/{id} removes the conversation outright and cancels its pending webhook deliveries, so no conversation.cancelled event is emitted for it. If you need a record of the cancellation, capture it before issuing the delete.

Rescheduling a call attempt yourself

PATCH /conversations/{id}/call-attempts/{attemptId} emits conversation.rescheduled with reason: "API_REQUEST". Because this endpoint moves an existing attempt rather than ending one and creating another, the same callAttemptId appears in both previousAttempt and nextAttempt, and previousAttempt.status is SCHEDULED — no call took place. This is the only reason value where that holds; for every other reason the two IDs differ.

Conversation Event Ordering

A conversation emits conversation.started once per call attempt, so multiple starts are normal. Exactly one terminal event is emitted per conversation — either conversation.completed, conversation.cancelled, or conversation.failed. Two representative sequences:
Before these events existed, retries and cancellations were silent — an integrator saw repeated start events with no explanation for the gaps, and a conversation that exhausted its attempts never produced a terminal event at all. If your handler infers state from event ordering, subscribe to both new events.

Analysis Events Payload

analysis.general.overallRating is null when the model that scores the conversation could not be reached, and scoringIncompleteReason says why. A null rating means no score was produced, not a bad one, so do not coerce it to 0. On a model_unavailable run the analysis is re-run automatically and analysis.completed fires again once the score exists, which means you can receive this event more than once for the same conversation. See Analysis Structure.

Failed Event Payloads

Failed events include an error message:

Ping Event Payload

Most webhook payloads contain minimal data (IDs and status), but the analysis.completed event includes the full analysis results and the analysisPdfBase64 Base64-encoded PDF report (when generation succeeds). For other details like transcripts, use the Conversation API to fetch the complete resource.

Per-Company Webhooks

For ATS integrations managing multiple companies, you can register webhooks per company by specifying the companyId parameter when creating a webhook. This enables:
  • Company-specific endpoints: Each company can have its own webhook URL (e.g., different subdomains)
  • Selective event delivery: Webhooks with companyId only receive events for that specific company
  • Global webhooks: Webhooks without companyId receive events for all companies accessible by the API key

Example: Per-Company Setup

Filtering by Company

When listing webhooks, you can filter by company:
Note: When filtering by companyId, only company-specific webhooks are returned. Global webhooks (those without a companyId) are excluded from filtered results. To see all webhooks including global ones, omit the companyId parameter.

Required Scopes

Security

Signature Verification

Every webhook request includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify this signature to ensure requests are from InstaView. The signature format is: sha256=<hex-encoded-signature>

Request Headers

Every webhook request includes these headers: Plus any custom headers you configured when creating the webhook.

Secret Management

Store your signing secret securely! The signing secret is only returned once when you create a webhook configuration. If lost, you must delete and recreate the webhook to get a new secret.
Best practices for secret management:
  • Store secrets in environment variables or a secrets manager
  • Never commit secrets to version control
  • Rotate secrets periodically by creating new webhooks
  • Use different secrets for development and production

Delivery Guarantees

At least once, for as long as the retry budget lasts. Every event InstaView records is attempted against each subscribed endpoint, and may be attempted more than once — so design your handler to tolerate a repeat; see Handling duplicates for the keys that collapse them. It is not an unconditional promise of arrival: an endpoint that keeps failing exhausts the retry budget described in Retry Logic, and the delivery is then marked failed and abandoned. What InstaView guarantees is that no recorded event is ever silently dropped on our side — every one is attempted, and a failure to deliver is a visible failed delivery rather than an event that quietly never existed. What “records” means matters, because it is what the guarantee rests on. The moment a conversation is cancelled, an analysis finishes, or any other event occurs, InstaView writes that fact to durable storage in the same database transaction as the change itself. Building the payload, resolving which endpoints are subscribed, and queueing the request all happen afterwards, and all of those steps are retried until they succeed — they are internal, so nothing about your endpoint can exhaust them. Only the HTTP delivery itself has a finite budget. So:
  • If the underlying operation is rolled back, no event is sent. There is no window in which you are told a conversation was cancelled when it was not.
  • If the operation commits, the event is owed to you even if InstaView restarts, loses a database connection, or fails to read the conversation in the instant afterwards. Nothing between the commit and the request can silently discard it.
Ordering is not guaranteed. Events are delivered independently and retried independently, so a retried conversation.started can arrive after the conversation.completed that followed it. Use the timestamps and identifiers in the payload rather than arrival order. A delivery is in flight only once. Each delivery attempt is claimed exclusively before the request goes out, so overlapping internal retry passes cannot send the same delivery twice in parallel. Duplicates you do see are genuine retries after a failure or timeout, and carry the deliveryId that lets you recognise them. Latency. Ordinary events are sent as soon as their transaction commits, typically within a second. Bulk events — the conversation.cancelled fan-out from a cancelled flow run, which can be thousands of conversations — are queued and drained in batches, so expect those to arrive over the following minutes rather than instantly.

Retry Logic

If your endpoint fails to respond with a 2xx status code, InstaView automatically retries delivery with exponential backoff:
1

Immediate Attempt

First delivery attempt immediately when event occurs
2

Retry 1

After a short delay if first attempt fails
3

Retry 2

After a longer delay if second attempt fails
4

Retry 3

Final attempt with maximum delay
After all retries are exhausted, the delivery is marked as failed. Each attempt reuses the same deliveryId, so a retry is always recognisable as a repeat of the attempt before it.

Circuit Breaker

To protect both your system and ours, InstaView implements a circuit breaker pattern:
  • If a webhook consistently fails, it will be automatically disabled
  • You can monitor the consecutiveFailures count in the webhook configuration
  • When disabled, the circuitOpenedAt timestamp indicates when the circuit opened
  • Use the Reset Circuit Breaker endpoint to re-enable the webhook after fixing issues

Handling Duplicates

There are two different kinds of duplicate, and they need different keys. The same delivery arriving twice — a retry after your endpoint timed out, or after it answered non-2xx. Both copies carry the same deliveryId. Track the deliveryId values you have processed and ignore a repeat, as in Be Idempotent below. The same event delivered to two of your endpoints — you registered a webhook on an ATS parent company and on one of its children, so an event in the child matches both configurations. These are genuinely separate deliveries with different deliveryIds, and deliveryId will not collapse them. Match instead on the event name plus the identifier of the occurrence the payload describes:
Do not key on data.conversationId alone for the per-attempt events. One conversation emits conversation.rescheduled once per retry and conversation.started once per attempt; treating those as duplicates of each other silently drops events you wanted. Only conversation.cancelled is once-per-conversation.
If you register a single endpoint per company — the common case — neither situation beyond ordinary retries arises, and deliveryId on its own is enough.
Both keys are stable across an InstaView restart or failover. Delivery is recovered from durable storage rather than from memory, so a redeployment mid-flight resumes the same delivery rather than starting a new one under a new deliveryId.

Endpoint Requirements

Your webhook endpoint must:
Return a 2xx response within 30 seconds. For long-running tasks, acknowledge receipt immediately and process asynchronously.
Handle duplicate deliveries gracefully using the deliveryId:
Production webhook URLs must use HTTPS. HTTP is only allowed for localhost during development.
Any 2xx status code indicates successful receipt. Retry behavior depends on the status code:

Managing Webhooks

Create a Webhook

Per-Company Webhooks: For ATS integrations managing multiple companies, you can specify companyId when creating a webhook. This allows you to register separate webhooks per company (e.g., different subdomains). If omitted, the webhook will receive events for all companies accessible by the API key (global webhook).

List Webhooks

Update a Webhook

Delete a Webhook

Test a Webhook

Send a test ping to verify connectivity:
To test a webhook using the test endpoint above, it must be subscribed to the ping event.

Testing Conversation Events with Test Conversations

To exercise the conversation events end to end (conversation.completed, analysis.completed), create a conversation with the isTest flag — it completes immediately and consumes no minutes:
Useful for:
  • Exercising your handler’s completion path
  • Checking that analysis data is parsed and stored correctly
  • End-to-end testing without waiting for a real call
  • Development and debugging without consuming billing minutes
See Create Conversation for the full detail on test mode.

Reset Circuit Breaker

If your webhook was disabled due to consecutive failures:

Custom Headers

You can configure custom headers to authenticate with your endpoint:
Security: Sensitive headers (Authorization, X-API-Key, X-Secret, API-Key) are automatically encrypted at rest. When listing webhooks, these header values are masked for security.

Best Practices

Verify Signatures

Always verify the X-Webhook-Signature header to ensure requests are authentic

Respond Fast

Return 200 immediately and process asynchronously for long-running tasks

Handle Duplicates

Use deliveryId for idempotency - you may receive the same event multiple times

Monitor Health

Check consecutiveFailures and circuitOpenedAt to catch issues early

Troubleshooting

Possible causes: - Webhook is not subscribed to the event type - Webhook is disabled (isActive: false) - Circuit breaker is open (check circuitOpenedAt) Solutions: - Verify event subscriptions in webhook configuration - Check if webhook is active - Reset circuit breaker if needed
Possible causes: - Using wrong signing secret - Modifying payload before verification - Encoding issues Solutions: - Verify you’re using the original signing secret - Verify the raw request body, not parsed JSON - Ensure UTF-8 encoding
Cause: Too many consecutive failures triggered the circuit breaker Solutions: 1. Fix the underlying issue (endpoint availability, authentication, etc.) 2. Use the test endpoint to verify connectivity 3. Reset the circuit breaker
Possible causes: - Event occurred before webhook was configured - Event type not in subscription list Note: Webhooks only deliver events that occur after configuration. For historical data, use the API to poll for past events.

Next Steps

Webhook Resource

Detailed webhook configuration reference

API Reference

Complete webhook API documentation

Conversations

Learn what a conversation carries

Error Handling

Handle webhook delivery errors