# Best Practices
Source: https://docs.quarterzip.ai/best-practices
## Deciding where to place your agents
With an SDK you can place a link to launch your agent from anywhere in your product. Increased agent entry points is the key to drive user activation. Here are several examples of places you can place your Call to Actions to launch your agent:
| Pages and Flows | Use Case | Description |
| - | - | - |
| **(Default)** Floating Action Button | Onboarding Support Feature Discovery | At the bottom right-hand corner of your product, a circle with a question-mark will appear. And you can use Google Tag Manager to add your agents to this element. |
| New User Modals | Onboarding | Provide new users support right away |
| Onboarding Center | Onboarding | Target onboarding focused users in your onboarding center |
| Help Center | Support Onboarding | Target users who need support |
| AI Chat Bots | Support Onboarding | Provide your users the option to switch to a voice modality within your AI Chat Bot |
| Sidebar | Support Onboarding | Offers high visibility support at any stage of the user journey |
| New Feature / Release Modal | Feature Discovery | Introduce new features with the ability to explore them together |
| Empty States | Onboarding Feature Discovery | Offer support on features that are new to the user
Note: This should be paired with another, more persistent option to allow your users to return to this |
## Call-to-Action component
Call-to-Action (CTA) components are how users launch your agents inside your product.
When a user selects a CTA, a **small, draggable call window** appears on top of your product UI. This window shows the **agent’s name** and a **button to start the call**.
The default CTA is a **Floating Action Button**, but you can create custom, brand-aligned components to match your product.
## Customize your UI
We recommend customizing the components that launch your Quarterzip agents to provide more context and increase engagement. High-conversion components include:
#### Banners
**Full-width** High visibility. Ideal for guiding new users through onboarding.
**Medium** Draws attention without dominating the interface. Works well in onboarding flows, support centers, or for contextual help and feature discovery.
**Small** Lightweight and unobtrusive. Ideal for sidebars to provide persistent help throughout the user journey.
#### Cards
**Large** Works well in dashboards or product layouts. Provides space for a short description alongside the CTA.
**Small** Compact and flexible. Best for side panels, help sections, or areas with limited space where support should still be easy to find.
## Choosing the right copy
Your CTA copy should clearly signal **what the experience is** and **why a user should start a call**.
Below are examples of what works and what to avoid.
#### What works
| Tip | Example |
| - | - |
| **Make it clear this is a voice experience** | Use phrases like “Join a Call” or “Talk to an Agent.” |
| **Highlight that the experience is personal** | “1:1 onboarding”, “Personalized support session.” |
| **Create Urgency** | “Get support now,” “Join a call,” “Talk to an agent.” |
| **Provide context for the help offered** | Tell users what the agent will help with: onboarding, setup, troubleshooting, support. |
| **Support international users** | “Get support in your language.” |
| **Use clear support icons** | Question mark or phone icons perform well and signal help to users. |
| **Set expectations for the booking** | Let users know they can schedule a session in their calendar for a later time. |
#### Avoid
| Avoid | Reason |
| - | - |
| **Not clearly stating that this is a voice call** | If users don’t understand they are starting a call, engagement and call completion drop. |
| **Using terms like “Product Tour”** | This signals a **static, non-personalized experience**, which lowers conversion. |
| **Ambiguous icons** | Icons like **microphones** or **headsets** often perform poorly because they don’t clearly signal support or help. |
## Signaling scheduling in your CTAs
Users can **schedule a call with your agent for a later time**, added directly to their calendar. This lets you meet users **when it’s most convenient for them** — not only in the moment they happen to see a CTA.
A standard CTA like “Join a Call” implies an **immediate, on-the-spot conversation**. If scheduling isn’t signaled, users who aren’t ready to talk right now will simply dismiss the CTA and **miss the opportunity to book a call entirely**. Whenever scheduling is available, make it explicit in your copy — and where it fits, offer both the immediate and the scheduled path.
#### What works
| Tip | Example |
| :- | :- |
| **Name the scheduling option directly** | “Book a call for later,” “Schedule a time that suits you.” |
| **Offer both now and later** | “Talk now — or book a time that works for you.” |
| **Reinforce convenience** | “Pick a time that fits your calendar,” “Reserve a 1:1 session when you’re ready.” |
| **Set expectations for the booking** | Let users know the call is added to their calendar and what it will cover. |
#### Avoid
| Avoid | Reason |
| :- | :- |
| **Only offering an immediate call** | Users who can’t talk right now leave without booking, and you lose the chance to reach them later. |
| **Hiding scheduling behind the call window** | If “schedule” only appears after a user starts a call, most users never discover it. Surface it upfront. |
| **Vague timing language** | Phrases like “Connect soon” don’t tell users they can pick a specific time on their own calendar. |
# Changelog
Source: https://docs.quarterzip.ai/changelog
New features and updates to Quarterzip
## Call Context
Pass extra information about a user directly to the agent at the start of a call — their plan, country, or handover notes from another tool like Intercom. The agent uses this context to make the conversation more seamless from the first message.
See [SDK](/deploy-quarterzip-sdk#passing-additional-context-to-the-agent) for details.
***
## Webhooks
Receive real-time event notifications from Quarterzip. Subscribe to call events and pipe data into your own systems — CRMs, analytics tools, or internal workflows.
***
## Mobile Call
Quarterzip now supports mobile call experiences, enabling users to start a guided session from a mobile device.
# Getting Started
Source: https://docs.quarterzip.ai/getting-started
Quarterzip agents can be deployed in two ways:
Shareable URL to a Quarterzip-hosted agent. Embed anywhere.
Install a Quarterzip agent inside your product via an Javascript NPM package
The [**Link Solution**](/link) is the fastest way to deploy to users. Every agent has a Quarterzip-hosted URL that can be shared however best suits your product, for example placed in a website button, email, marketing page, or help center. The link takes users to a page customised to your branding, where they can begin a call with your Quarterzip agent.
Our [**SDK**](/deploy-quarterzip-sdk) keeps the experience entirely inside your product. Users are able launch the call from within your own UI, and a draggable interactive player lets them continue working without leaving the tab.
## Choosing the *right* Deployment Type
Most teams start first with the [**Link Solution**](/link) to validate their use case, before graduating to the [**SDK**](/deploy-quarterzip-sdk) for a tighter in-product user experience.
An [**SDK**](/deploy-quarterzip-sdk) integration requires that your site is predominately a **Single-Page Application (SPA)**. Multi-page applications, hard page navigations or page refreshes will terminate the call with an agent. Users can start new calls, however this can be a worse experience for your users.
### Example: Quarterzip embedded via SDK
Explore this [example app](https://sdk-example-app.vercel.app/) using the Quarterzip embedded SDK. The agent is accessible from the bottom-left button *"Talk to an HR agent"*. The button is controlled & styled by the app's author. The Quarterzip Agent appears when a user interacts with the button. A draggable, resizable player sits over the site, and a picture-in-picture window keeps the session alive if the user navigates to another tab once the call has started.
## Concepts
### Document Picture-in-Picture (PiP)
Quarterzip agent's rely on a browser technology called Document Picture-in-Picture that provides users with an always-on-top, draggable player window that provides agent controls.
# Link
Source: https://docs.quarterzip.ai/link
The fastest way to connect users with your Quarterzip agent. Each agent gets a dedicated URL — embed it anywhere and users can start a guided call in seconds.
New to Quarterzip? Be sure to visit the [Getting Started](/getting-started) page before continuing with this section.
## Where to embed your link?
Your agent URL can be placed anywhere users interact with your product. Common entry points:
* **In-product button -** A contextual help button inside your application
* **Email CTA -** Onboarding, re-engagement, or expansion emails
* **Marketing site -** Landing pages, pricing pages, or demo CTAs
* **Help centers -** Provide 1:1 personalized support
## What is the User's Experience?
The user is taken to the Quarterzip-hosted page for the agent. This page is customised with your logo & branding.
First-time users are asked to grant microphone access permissions. The Quarterzip agent introduces themselves & handles any immediate questions or needs from the user.
The user is prompted to share their tab, window, or screen, and to open the product if required.
The agent's player appears early in the call in a Picture-in-Picture (PiP) window. An always-on-top, draggable window from which the user can control the call where ever they go.
After resolving any immediate issues the user may raise, the Quarterzip agent offers the user through guides you've crafted; e.g. first-time onboarding, new-feature discovery, deeper technical support.
## Distribution
### Embedding the link in a button
The most common pattern is a plain anchor tag styled as a button. Keep the CTA copy action-oriented and clear that it starts a voice call:
```jsx theme={null}
Talk to an agent
```
See [Best Practices](/best-practices) for guidance on CTA copy — phrases like "Talk to an agent" or "Join a call" consistently outperform generic labels like "Get help".
### Using the link in email CTAs
Quarterzip links work anywhere a URL is valid — including email. Drop the link into onboarding sequences, re-engagement campaigns, or post-trial follow-ups:
```jsx theme={null}
Book a 1:1 onboarding session →
```
### Linking from a help center
Add the agent link to help center articles as a contextual escalation path. Place it at the top for high-complexity topics, or at the bottom as a fallback:
```markdown theme={null}
Still stuck? [Talk to an onboarding agent](https://app.quarterzip.ai/a/YOUR_AGENT_ID) for live, step-by-step help.
```
### Linking from in-app empty states
Empty states are high-intent moments — the user is on a feature page but hasn't taken action yet. A Quarterzip link here offers guided help exactly when it's needed, and can be paired with a more persistent entry-point (e.g. a sidebar button) so users can return to the agent after their first session.
```jsx theme={null}
No reports yet
Not sure where to start? An agent can walk you through it.
```
### Sharing links with multiple stakeholders
Because the Quarterzip link requires no login, it's easy for a buyer champion to forward it to other stakeholders. Anyone with the link can start a session. This makes it well-suited for:
* **Sales rooms** (Arrows, Dock) — embed the link so champions can share it internally
* **Proposals and decks** — add the link as a resource for evaluators who want a live demo
* **Slack or Teams messages** — paste the link directly into a channel for team onboarding
***
## Link API Reference
### URL Query Parameters
The Quarterzip agent link can receive additional information through the URL's query parameters. Providing a `userId` will allow users to skip the form on the agent's landing page.
| Parameter | Description |
| :- | :- |
| `userId` | The user's unique identifier — typically their email address or your internal user ID |
| `userDisplayName` | The user's name or username to display in the call log |
| `context` | A free-text string input for providing additional context to the agent at the start of the call. The value must be **URI-encoded** and is capped at **2,000 characters**. |
```javascript theme={null}
const agentUrl = new URL('https://app.quarterzip.ai/a/YOUR_AGENT_ID');
agentUrl.searchParams.set('userId', user.email);
agentUrl.searchParams.set('userDisplayName', user.displayName);
const context = `Plan: ${user.plan}. Country: ${user.country}. Signed up: ${user.createdAt}.`;
agentUrl.searchParams.set('context', context);
// e.g. https://app.quarterzip.ai/a/YOUR_AGENT_ID?userId=jane%40company.com&userDisplayName=Jane+Smith&context=Plan%3A%20Pro...
```
### Passing context to the agent
The agent link accepts a `context` query parameter — a free-text string the agent receives at the start of the call. Use it to pass information about the user so the conversation can be personalised without the user having to repeat themselves.
Use cases include:
* **Current plan** — so the agent knows what features are available
* **Country or locale** — for region-specific guidance
* **Handover notes** — if the user is being transferred from another tool like Intercom
The value must be **URI-encoded** and is capped at **2,000 characters**.
# Network requirements
Source: https://docs.quarterzip.ai/network-requirements
What your firewall needs to allow for Quarterzip calls to connect and carry audio.
Quarterzip calls run in the browser over WebRTC, using [Daily](https://www.daily.co) for call media. Most networks work without changes.
If callers on your network see **"We couldn't connect the call audio"** or **"The connection failed"**, your firewall or proxy is blocking part of the call. This page is for the IT team that manages that network.
## Choose a configuration
There are two supported configurations. Pick one, and apply it completely.
| | Minimal (TCP media) | Recommended (UDP) |
| - | - | - |
| What it opens | Web traffic and a relay over TLS on port 443 | Adds direct UDP to the call servers |
| Call quality | Works, with added latency; worse on lossy networks | Best: lowest latency, handles packet loss well |
| UDP to call servers | **Must be fully blocked** | **Must be fully allowed** |
**Do not partially allow UDP.** This is the most common cause of failed calls.
If a firewall lets connection checks through but drops the encrypted media that follows, the browser sees UDP working, picks it, and no audio ever arrives. Either allow UDP to the call servers fully (Recommended) or block it fully (Minimal).
## Step 1: Allow the required hosts
These rules are needed for **both** configurations.
### Web and call setup (TCP 443)
| Host | Used for |
| - | - |
| `*.quarterzip.ai` | The Quarterzip app, API and call pages |
| `*.daily.co` `*.dailywebrtc.com` `*.dailywebrtc.net` | Call setup |
| `*.wss.daily.co` `*-wss.daily.co` `*.wss.dailywebrtc.com` `*-wss.dailywebrtc.com` `*.wss.dailywebrtc.net` `*-wss.dailywebrtc.net` | Call signalling |
| `b.daily.co` `c.daily.co` `b.dailywebrtc.com` `c.dailywebrtc.com` `b.dailywebrtc.net` `c.dailywebrtc.net` | Call resources |
| `gs.daily.co` `gs.dailywebrtc.com` `gs.dailywebrtc.net` | Call routing |
| `prod-ks.pluot.blue` | Connection negotiation |
`dailywebrtc.com` and `dailywebrtc.net` are Daily's fallback domains. A wildcard on `daily.co` does not cover them, so allow them alongside it.
### STUN connection checks
| Host | Protocol and ports |
| - | - |
| `stun.cloudflare.com` | UDP 3478 and UDP 53 |
| `global.stun.twilio.com` (or `*.stun.twilio.com`) | [Ports listed by Twilio](https://www.twilio.com/docs/stun-turn/regions) |
## Step 2: Apply one configuration
The smallest set that connects a call on almost any network. All call media is relayed over TLS on port 443, which looks like ordinary HTTPS to most firewalls.
**1. Allow the TLS relay hosts** over **TCP/TLS 443**, in addition to the required hosts:
* `turn.cloudflare.com`
* `*.turn.twilio.com`
**2. Block outbound UDP for call traffic completely:**
| Block | Hosts | Ports |
| - | - | - |
| Call servers | `*.wss.daily.co` and the other signalling hosts above | UDP 23000-26999 and 40000-49999 |
| Relays | `turn.cloudflare.com`, `*.turn.twilio.com` | UDP 3478 and 53 |
Calls then go straight to the TLS relay instead of trying a UDP path that will not carry media. STUN over UDP stays allowed.
**What you give up:** relayed TCP adds latency and copes badly with packet loss. One-to-one voice calls are usually fine; quality drops on lossy networks.
**1. Allow the TLS relay hosts** from Option 1 over **TCP/TLS 443**, in addition to the required hosts. Do **not** apply Option 1's UDP block.
* `turn.cloudflare.com`
* `*.turn.twilio.com`
**2. Allow direct media to the call servers:**
| Hosts | Ports |
| - | - |
| `*.wss.daily.co` `*-wss.daily.co` `*.wss.dailywebrtc.com` `*-wss.dailywebrtc.com` `*.wss.dailywebrtc.net` `*-wss.dailywebrtc.net` | UDP 23000-26999 UDP 40000-49999 TCP 40000-49999 |
Allow this in both directions for the full call. Calls then use the low-latency UDP path and fall back to the TLS relay only when they need to.
If your firewall cannot use wildcard hostnames, or applies hostname rules poorly to UDP, allowlist by IP using Daily's [published IP list](https://ip-info.daily.co/ips/ip-info.json) (the `sfu` and `web` hosts). The list changes over time, so poll it on a schedule. See Daily's [Networking Guide](https://docs.daily.co/docs/guides/privacy-and-security/corporate-firewalls-nats-allowed-ip-list) for details.
## Step 3: Check TLS inspection and VPNs
* **Do not inspect call traffic.** Exclude STUN, TURN and the call servers above from any decrypt-and-re-encrypt or deep packet inspection rule. Call media is already encrypted end to end, so inspection has nothing to read, and it breaks the connection.
* **Use split tunnelling for VPNs and secure web gateways** (for example Zscaler) so call traffic bypasses the tunnel.
* **Route call traffic the same way on every operating system.** Some clients tunnel TCP but send UDP directly out of the local network. This recreates the partial-UDP problem above on some machines only.
## Still not working?
1. Run Quarterzip's [network test](https://network-debugger.vercel.app/) from an affected machine to see which connections fail.
2. Email [support@quarterzip.ai](mailto:support@quarterzip.ai) with the results and the time of the failed call.
# Overview
Source: https://docs.quarterzip.ai/overview
Quarterzip enables you to place conversational AI voice agents inside your products, offering unique & engaging experiences for your users to achieve onboarding, feature discovery, & resolve support issues.
Your users can join a call, & optionally share their screen, with your Quarterzip agent who will guide users through the goals you've configured.
Conversations are natural. Quarterzip agents pause for questions, drill deeper into issues & topics, and dynamically adapt to solve your customer's needs as they arrive.
## How does Quarterzip work?
Quarterzip agents guide users through your product step by step.
Users start a call with an agent, share their screen, and receive real-time guidance as they move through workflows. The agent can see what the user sees, answer questions, and talk them through each step.
Because the agent has visual context, it can provide relevant, in-the-moment support instead of generic instructions.
Quarterzip is designed to help without getting in the way. A picture-in-picture call window lets users manage the conversation while continuing to use your product or navigate third-party tools.
## How can the agent see the screen?
Quarterzip sees the user's screen the same way a screen-share works in a video call. This gives the agent real-time visual context so it understands what the user is looking at and can provide relevant, step-by-step guidance.
Users can start and stop screen sharing at any time. Quarterzip never captures screen content without an active share.
## Can agent's guide users outside of my product?
Yes, Quarterzip agents can help users with tasks within and outside of your product; i.e. third-party sites and tools. This helps with use-cases that involving users leaving your product & completing steps in another, such as connecting or authorising cross-site integrations.
The Quarterzip agent player can follow the user across sites, tabs, windows, & monitors.
# Resources
Source: https://docs.quarterzip.ai/resources
[Terms of Service](https://www.quarterzip.ai/legal/terms-of-service)
[Privacy Policy](https://www.quarterzip.ai/legal/privacy-policy)
[Trust Center](https://trust.quarterzip.ai/)
[Security and Trust Overview](https://www.quarterzip.ai/legal/security-and-trust-overview)
# SDK
Source: https://docs.quarterzip.ai/sdk
New to Quarterzip? Be sure to visit the [Getting Started](/getting-started) page before continuing with this section.
## Quickstart Plan
1. [Configure your Quarterzip Workspace for SDK](#configure-your-quarterzip-workspace-for-sdk)
2. [Install the Quarterzip SDK](#install-the-quarterzip-sdk)
3. [Grant Security Permissions](#grant-security-permissions)
4. [Trigger the Quarterzip Agent](#trigger-the-quarterzip-agent)
## Configure your Quarterzip Workspace for SDK
Visit the SDK Settings (`Settings -> SDK`) in your Quarterzip workspace. This page provides the configuration options to restrict *who* may serve your agent on your behalf.
* **Workspace Token:** a public token used to authenticate your site to Quarterzip.
* **Allowed Origins:** an optional list of site origins that are authorised to serve your Quarterzip agent. This list may include wildcards for subdomains, i.e. `https://*.example.com`
Then visit the My Agents page and select your agent.
* **Agent ID:** A 6 alpha-numeric character ID to identify your agent. Select the menu button in the top right and click share my agent. From here, the agent\_id is the 6 alpha-numeric characters at the end of the url, after the `/d/` .
## Install the Quarterzip SDK
### CDN Script
```javascript theme={null}
```
### NPM Package
The NPM package is NOT the recommended installation method for Quarterzip. Please use the CDN script or contact support before using this NPM package installation.
```shellscript theme={null}
pnpm add @quarterzip.ai/sdk
```
## Grant Security Permissions
The Quarterzip SDK operates in a security sandbox separate from your site. In order for your Quarterzip agent to communicate with your users, the following permissions must be granted if enforced.
### **CSP requirements**
Only relevant if your page sends a `Content-Security-Policy` header, then it must permit Quarterzip's SDK origin to be framed.
```text theme={null}
Content-Security-Policy:
frame-src https://sdk.quarterzip.ai;
child-src https://sdk.quarterzip.ai;
```
### **Permissions Policy**
Only relevant if your page sends a `Permissions-Policy` header, then it must permit Quarterzip's SDK origin the following permissions:
```text theme={null}
Permissions-Policy:
microphone=(self "https://sdk.quarterzip.ai"),
display-capture=(self "https://sdk.quarterzip.ai"),
autoplay=(self "https://sdk.quarterzip.ai"),
speaker-selection=(self "https://sdk.quarterzip.ai"),
storage-access=(self "https://sdk.quarterzip.ai")
```
Learn more at [Security](/security).
## Trigger the Quarterzip Agent
Call `window.Quarterzip.open()` from any user interaction; i.e. a button-press. Visit [SDK API Reference](/sdk/api) for more details.
```jsx CDN theme={null}
window.Quarterzip.open({
workspaceToken: 'YOUR_WORKSPACE_TOKEN',
agentId: 'YOUR_AGENT_ID',
user: {
id: 'your_internal_user_id'
email: 'jane@company.com',
displayName: 'Jane Smith',
}
});
```
```jsx NPM theme={null}
Quarterzip.open({
workspaceToken: 'YOUR_WORKSPACE_TOKEN',
agentId: 'YOUR_AGENT_ID',
user: {
id: 'your_internal_user_id'
email: 'jane@company.com',
displayName: 'Jane Smith',
}
});
```
It's important to verify integration by conducting a call with the agent; completing expected steps such as granting microphone permissions, screen-sharing and ending the call.
Set yourself up for success.
React and receive call data.
## Advanced patterns
### Passing additional context to the agent
The `context` parameter may be used to provide additional information to the agent to enhance the conversation; e.g. the user's plan, country, or handover notes from another support tool. The agent only receives this information if the user decides to start the call. The amount of context is capped at **2,000** characters.
```jsx CDN theme={null}
window.Quarterzip.open({
workspaceToken: 'YOUR_WORKSPACE_TOKEN',
agentId: 'YOUR_AGENT_ID',
user: { id: user.id, email: user.email, displayName: user.displayName },
context: `Plan: ${user.plan}. Country: ${user.country}.`,
});
```
```jsx NPM theme={null}
Quarterzip.open({
workspaceToken: 'YOUR_WORKSPACE_TOKEN',
agentId: 'YOUR_AGENT_ID',
user: { id: user.id, email: user.email, displayName: user.displayName },
context: `Plan: ${user.plan}. Country: ${user.country}.`,
});
```
### Conditionally present Quarterzip agents
Most customers prefer to show agents to users based on feature-flags or user-attributes.
Screen-sharing requires the user's browser to have the `getDisplayMedia()` web API, which is unavailable on most mobile devices and can help determine which users to filter out.
```javascript CDN theme={null}
import { useFeatureFlagEnabled } from 'posthog-js/react';
function maybeOpenAgent() {
// Feature-flag Quarterzip agent
const enabled = useFeatureFlagEnabled('quarterzip-sdk');
if (!enabled) return null;
// Does browser support screen-sharing
const canScreenShare = !!(navigator.mediaDevices?.getDisplayMedia);
if (!canScreenShare) return null;
// Show Quarterzip agent based on a user-attribute; i.e. first-time visit
const user = getCurrentUser();
if (user.onboardingComplete) return null;
return (
);
}
```
```jsx NPM theme={null}
import { Quarterzip } from '@quarterzip.ai/sdk';
import { useFeatureFlagEnabled } from 'posthog-js/react';
function maybeOpenAgent() {
// Feature-flag Quarterzip agent
const enabled = useFeatureFlagEnabled('quarterzip-sdk');
if (!enabled) return null;
// Does browser support screen-sharing
const canScreenShare = !!(navigator.mediaDevices?.getDisplayMedia);
if (!canScreenShare) return null;
// Show Quarterzip agent based on a user-attribute; i.e. first-time visit
const user = getCurrentUser();
if (user.onboardingComplete) return null;
return (
);
}
```
### Closing on route change
End the call when a user navigates away from a specific area (React Router v6 example):
```jsx CDN theme={null}
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function useCloseAgentOnLeave(protectedPaths = []) {
const location = useLocation();
useEffect(() => {
const isProtected = protectedPaths.some(p => location.pathname.startsWith(p));
if (!isProtected) window.Quarterzip?.close();
}, [location.pathname]);
}
useCloseAgentOnLeave(['/onboarding']);
```
```jsx NPM theme={null}
import { Quarterzip } from '@quarterzip.ai/sdk';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function useCloseAgentOnLeave(protectedPaths = []) {
const location = useLocation();
useEffect(() => {
const isProtected = protectedPaths.some(p => location.pathname.startsWith(p));
if (!isProtected) Quarterzip?.close();
}, [location.pathname]);
}
useCloseAgentOnLeave(['/onboarding']);
```
### React wrapper hook
```jsx CDN theme={null}
import { useCallback } from 'react';
import { useCurrentUser } from '../hooks/useCurrentUser';
export function useQuarterzip(agentId) {
const user = useCurrentUser();
const open = useCallback(() => {
window.Quarterzip?.open({
workspaceToken: process.env.REACT_APP_QUARTERZIP_TOKEN,
agentId,
user: { email: user.email, displayName: user.displayName, id: user.id },
});
}, [agentId, user]);
const close = useCallback(() => window.Quarterzip?.close(), []);
return { open, close };
}
```
```jsx NPM theme={null}
import { Quarterzip } from '@quarterzip.ai/sdk';
import { useCallback } from 'react';
import { useCurrentUser } from '../hooks/useCurrentUser';
export function useQuarterzip(agentId) {
const user = useCurrentUser();
const open = useCallback(() => {
Quarterzip?.open({
workspaceToken: process.env.REACT_APP_QUARTERZIP_TOKEN,
agentId,
user: { email: user.email, displayName: user.displayName, id: user.id },
});
}, [agentId, user]);
const close = useCallback(() => Quarterzip?.close(), []);
return { open, close };
}
```
## Compatibility
The Quarterzip sidekick uses the [Document Picture-in-Picture API](https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API) and as such is only compatible on web browsers that fully support the specification. Currently Quarterzip's Sidekick is only verified to work in Google Chrome Desktop 116 and above.
## What's Next?
Set yourself up for success.
React and receive call data.
# SDK API Reference
Source: https://docs.quarterzip.ai/sdk/api
SDK API reference and environment requirements
## `Quarterzip.open()`
Opens the Quarterzip agent panel. The call does not begin until the user clicks **Join**.
```typescript theme={null}
Quarterzip.open(args: OpenArgs, options?: OpenOptions): void
```
```typescript theme={null}
interface OpenArgs {
/** ID of the agent to call. */
agentId: string;
/** Your workspace's public token — safe to ship in client code, not a secret. */
workspaceToken: string;
/** Who is on the call. */
user: {
/** Required. Opaque or hashed identifier from your own system. */
id: string;
/** Optional. Only sent if you want it on the Quarterzip call record. */
email?: string;
/** Optional. Only sent if you want it on the Quarterzip call record. */
displayName?: string;
};
/** Optional free text handed to the agent as context for this call. */
context?: string;
/** Optional BCP-47 language tag. A hint for the default displayed language option */
locale?: string;
/** Optional event handler callbacks */
on?: {
/** Called when the user begins a call with the Quarterzip agent */
callStarted?: () => void;
/** Called when the call ends, for any reason */
callEnded?: () => void;
/** Called when the QZ player is closed post-call */
close?: () => (event: { reason: string }) => void;
/** Called when the call fails. When `fatal` is true, `callEnded` and `close` follow. */
error?: () => (event: { code: string; fatal: boolean }) => void;
}
}
interface OpenOptions {
/** Default console verbosity for every call. */
logLevel?: LogLevel;
/** Allow the floating Picture-in-Picture window. Defaults to true. */
pip?: boolean
```
```tsx CDN theme={null}
// Must be called from a user gesture (e.g. a click handler).
callButton.addEventListener('click', () => {
window.Quarterzip.open(
{
agentId: 'YOUR_AGENT_ID',
workspaceToken: 'YOUR_WORKSPACE_TOKEN',
user: {
id: 'your_internal_user_id',
email: 'jane@example.com',
displayName: 'Jane Doe'
},
context: 'Viewing invoice INV-4471, plan: enterprise',
on: {
close: ({ reason }) => analytics.track('call_ended', { reason }),
error: ({ code, fatal }) => console.warn('Quarterzip call failed', code, fatal)
}
},
);
});
```
```tsx NPM theme={null}
```
### `locale`
Provide a [BCP-47 language tag](https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag) in this field to hint Quarterzip with the language preferences of the user. This merely decides the default language setting that will be presented to the user - they will still be able to select any of the languages Quarterzip supports.
If the locale supplied in the `locale` field is empty or not supported by Quarterzip, the picker defaults to the user's browser-configured locale. If this is also unavailable, it defaults to the default value configured for that agent.
The agent language will match the narrowest possible locale. For instance: Quarterzip currently does not support Quebecois agents, so `fr-CA` will resolve to `fr` . Quarterzip *does* support Australian English, so `en-AU` passes through unchanged.
**Supported Languages**
| Locale | Language |
| :- | :- |
| `ar` | Arabic |
| `bg` | Bulgarian |
| `cs` | Czech |
| `da` | Danish |
| `de` | German |
| `el` | Greek |
| `en` | English |
| `en-AU` | English (Australian) |
| `en-GB` | English (British) |
| `es` | Spanish |
| `fi` | Finnish |
| `fil` | Filipino (Tagalog) |
| `fr` | French |
| `hi` | Hindi |
| `hr` | Croatian |
| `hu` | Hungarian |
| `id` | Indonesian |
| `it` | Italian |
| `ja` | Japanese |
| `ko` | Korean |
| `ms` | Malay |
| `nl` | Dutch |
| `no` | Norwegian |
| `pl` | Polish |
| `pt` | Portuguese |
| `ro` | Romanian |
| `ru` | Russian |
| `sk` | Slovak |
| `sv` | Swedish |
| `ta` | Tamil |
| `th` | Thai |
| `tr` | Turkish |
| `uk` | Ukrainian |
| `vi` | Vietnamese |
| `zh` | Chinese |
### `on.close()`
```typescript theme={null}
/** Called once when the call finishes, whatever the reason. */
on.close?: (event: { reason: string }) => void;
```
| `reason` | description |
| - | - |
| `user` | The user closed the Quarterzip panel themselves, using its own close control. The standard way a call ends. |
| `integrator` | Your code ended it — you called `Quarterzip.close()`, or called `open()` again with a different agentId, which closes the running session before starting the new one. |
| `detached` | The panel was removed from the page, or the page navigated away, without anyone ending the call. Typically occurs when a single-page app unmounts the container, or due to a full page navigation. |
| `error` | A failure ended the session. See `onError()`. |
### `on.error()`
```typescript theme={null}
/** Called when the call fails. When `fatal` is true, `onEnd` follows. */
on.error?: (event: { code: string; fatal: boolean }) => void;
```
Currently, all events are `fatal: true`.
| `code` | description |
| :- | - |
| `frame-unreachable` | The Quarterzip iframe panel failed to fetch its contents. Typically due to your page's configuration; see [granting security permissions](/sdk#grant-security-permissions). |
| `frame-not-ready` | The iframe panel loaded but failed to signal as healthy. Retry with `open()`. If it persists, contact Quarterzip support with the `agentId` and approximate time. |
| `open-args-not-transferable` | Your `open()` arguments could not be sent to the panel. Review the expected arguments & their types. |
| `authentication-failed` | The session failed to authentication checks. Usually transient & safe to retry. |
| `agent-does-not-exist` | The `agentId` is unknown, or the `workspaceToken` is not valid for that agent. |
| `agent-not-accepting-calls` | The agent is configured not to take calls until the agent is re-enabled in the Quarterzip dashboard. Hide or disable your call entry point if you surface this. |
| `call-limit-reached` | A usage limit on the workspace has been reached. Contact Quarterzip to discuss billing. |
| `no-agents-available` | No agent could be assigned to the call as capacity was momentarily exhausted. Retry shortly thereafter. |
| `call-start-failed` | The call could not be started for a reason that isn't one of the more specific codes above. Generally transient; let the user retry. |
| `device-error` | The user's microphone could not be used: the permission prompt was denied or dismissed by the user, no input device is available, or the device is held by another application. The user has to fix this in their browser or OS. |
| `connection-failed` | The network or media transport dropped. |
| `call-failed` | The call failed for a reason other than connectivity. |
## `Quarterzip.close()`
```typescript theme={null}
Quarterzip.close(): void
```
```tsx CDN theme={null}
// Ends the active call and removes the iframe and any PiP window.
// Safe to call when nothing is open — it's a no-op.
window.Quarterzip.close();
```
```tsx NPM theme={null}
```
# Security
Source: https://docs.quarterzip.ai/security
Quarterzip uses secure and robust design principles to provide best in class security features for our customers.
Quarterzip is ISO/IEC 27001 certified, demonstrating our commitment to internationally recognized standards for information security management.
## Consent Approval Process
When starting a call users can decide which part of their screen and what audio they would like to share with Quarterzip. You can decide what the default share option should be in your agent settings.
User have three different options:
1. Share a single tab in their browser.
2. Share a single application window.
3. Share an entire screen.
The agent can only see their screen after the user has made a specific choice about what to share with the agent. By default most web browsers will ask permission the first time a website wants to see your screen.
If a user doesn't wish to share their screen the agent may still continue with the call ans guide the user with audio assistance only.
Similar to the screen the user must provide permission to the webpage to access the microphone.
The agent can only hear the user after permission to access the microphone is given.
## Authorization Using Google Identity
Quarterzip sessions are authenticated using [Google Identity Anonymous Auth](https://docs.cloud.google.com/identity-platform/docs/concepts-authentication). When the SDK is initialised inside a webpage it creates an anonymous identity that is stored locally in the session. This identity is used across calls on the same domain to maintain memory and call context.
When starting a call with the SDK you can provide an email, display name, and external identifier
## Model Training
By default Quarterzip does not use your data for foundational model training. Quarterzip does use recordings of calls (if enabled) to provide fine tuning of the knowledge base and agent guide.
## Data Processing and Retention
Data retention durations can be found in our [Security and Trust Overview](https://www.quarterzip.ai/legal/security-and-trust-overview) page, customer can opt-in to 90, 180, or 365 days of retention.
### Internal Testing Call: Visual Data
**Description of Processing:** For quality assurance and to monitor agent performance, a random sample of interactions has its raw media (audio and images) temporarily stored.
**Definition of Data Type:** Visual Data refers to video or image data taken from the user's screen, transmitted from a user's device during live communication sessions used by workspace users (Onboarding agent builders) to develop and train the agent. This does not pertain to video from the user's camera.
**Collection Enabled:** Opt-Out
**Retention:** If opted-in, the Customer may configure this to; 90, 180, 365 or indefinite
### End User Calls: Visual Data
**Description of Processing:** For quality assurance and to monitor agent performance, a random sample of interactions has its raw media (audio and images) temporarily stored.
**Definition of Data Type:** Visual Data refers to video or image data taken from the user's screen, transmitted from a user's device during live communication sessions for your customer's users (regarded as external end users). This does not pertain to video from the user's camera.
**Collection Enabled:** Opt-in
**Retention:** If opted-in, the Customer may configure this to; 90, 180, 365 or indefinite
### Audio and Transcripts
**Description of Processing:** Audio from interactions is processed into text transcripts. These transcripts serve as the direct input for Insight generation. They are stored temporarily to allow for reprocessing or support inquiries if needed.
**Definition of Data Type:** Call Audio and Artefacts include voice recordings, call transcripts, and content representative of the audio during the communication session.
**Collection Enabled:** Opt-Out
**Retention:** If opted-in, the Customer may configure this to; 90, 180, 365 or indefinite
### Analytics
**Description of Processing:** The transcripts and interaction metadata are distilled into the actionable Insights and Analytics that you see and interact with. This includes performance metrics, sentiment analysis, key topic identification, and trend data. This is the primary representation of your data within the Quarterzip platform.
**Definition of Data Type:** Analytics Data comprises operational metadata, usage information, system-generated events, and derived or insights associated with the use of the service. This includes identifiers, anonymised insights, timestamps, performance metrics, and aggregated interaction patterns visible within product interfaces. We create anonymised statistical data from Your Data and usage of our Services (for example, through aggregation). Once anonymised, we own that data and may use it for our own purposes, such as to provide and improve our Services, to develop new services or product offerings, to identify business trends, and for other uses we communicate to you. This may include making such anonymised data publicly available, provided it is not compiled using a sample size small enough to make underlying portions of Your Data identifiable.
**Collection Enabled:** Required
**Retention:** Indefinite
# Webhooks
Source: https://docs.quarterzip.ai/webhooks
Receive real-time, signed notifications when events occur in your Quarterzip workspace.
**Webhooks are currently in preview**. Seamless delivery is not guaranteed, and the API, payload shape, and behaviour are likely to change in future releases without notice.
Webhooks allows Quarterzip to provide you with real-time event notifications. By registering a secured URL endpoint, Quarterzip will send a signed `POST` request in response to certain events occurring in your workspace.
See support [Events](#events) below.
## Configuring Webhooks
Webhooks depend on you providing a URL endpoint to a secured HTTPS server that can receive & respond to delivered messages from Quarterzip. Testing can performed with online services such as [**webhook.site**](http://webhook.site), or against locally running servers using [ngrok](https://ngrok.com/) or [Cloudflare tunnels](https://developers.cloudflare.com/tunnel/).
1. In the Quarterzip dashboard, go to **Settings → Webhooks**.
2. Enter the **HTTPS** URL that should receive events.
3. Toggle **Enabled** on and save.
You can configure **one** endpoint per workspace.
## Secured with Signatures
Quarterzip signs every delivery using the open [Standard Webhooks](https://www.standardwebhooks.com/) specification. This includes a `webhook-signature` header value that you can verify against to ensure that the webhook message was delivered by Quarterzip and was not tampered in transit.
Verifying the signature is optional. Webhook message are not encoded.
### Verifying the signature
When you save a URL for the first time, Quarterzip generates a **signing secret.** Each request includes three headers:
| Header | Description |
| - | - |
| `webhook-id` | Unique ID for this delivery (for example, `call_completed_abc123`). |
| `webhook-timestamp` | Send time as a Unix timestamp (seconds). |
| `webhook-signature` | Space-separated list of `v1,` values. |
The official `standardwebhooks` libraries can be used to verify a webhook delivery with only a few lines of code.
```python Python theme={null}
# uv pip install standardwebhooks
from standardwebhooks import Webhook
# Generated from your workspace's SDK settings page
WEBHOOK_SECRET = "whsec_..."
def handle_webhook(raw_body: bytes, headers: dict[str, str]) -> None:
wh = Webhook(WEBHOOK_SECRET)
# Raises on an invalid signature or an out-of-tolerance timestamp.
payload = wh.verify(raw_body, headers)
if payload["type"] == "call.completed":
process_completed_call(payload["data"])
```
```ts Node / TypeScript theme={null}
// npm install standardwebhooks
import { Webhook } from 'standardwebhooks';
// The whole "whsec_..." string shown when you created or rotated the secret.
const WEBHOOK_SECRET = 'whsec_...';
// `rawBody` must be the exact bytes/string received, not a re-serialized object.
function handleWebhook(rawBody: string, headers: Record) {
const wh = new Webhook(WEBHOOK_SECRET);
// Throws on an invalid signature or an out-of-tolerance timestamp.
const payload = wh.verify(rawBody, headers) as { type: string; data: unknown };
if (payload.type === 'call.completed') {
processCompletedCall(payload.data);
}
}
```
### Rotating the secret
A new signing secret can be generated from the workspace's SDK settings page. Rotation takes effect immediately.
## Delivery, Retries & Timeouts
Return any `2xx` status code to acknowledge a webhook. Anything else — `4xx`, `5xx`, a connection error, or a timeout — is treated as a failed delivery and will be retried.
Acknowledge first, process later. Your endpoint should do the minimum needed to persist the event (write it to a queue or a table) and return 2xx immediately. Doing the work inline risks hitting the timeout, and a timeout means redelivering an event you have already processed.
Each delivery attempt is given 30 seconds. An attempt that hasn't completed in that window is abandoned and retried. A failed delivery is retried up to 7 times with exponential backoff with jitter, starting with a 15 second delay. After the final retry the event is dropped and the endpoint is considered unreachable - this will automatically disable the workspace's webhooks.
Retries reuse the same `webhook-id` header as the original attempt. You should expect duplicates even when nothing looks wrong. A response we never received — because it arrived after the timeout, or the connection dropped mid-response — is indistinguishable from a failure, so we may retry an event your endpoint already processed successfully.
## Best Practices
* While webhooks is in **preview**, ensure that your server's endpoint handler responds with a `2xx` status code. Consider wrapping the handler in a `try...except`, `try...catch`, or equivalent. Failing to do so risks the workspace's registered webhook to be automatically deactivated; see [Delivery, Retries & Timeouts](#delivery-retries-&-timeouts).
* Evaluate received messages by event `type`. The likelihood of new event types encourages the following pattern:
```python theme={null}
if payload["type"] == "call.completed":
process_completed_call(payload["data"])
elif payload["type"] == "call.recording.ready"
process_completed_call(payload["data"])
else:
print(f"Unsupported event type recieved: {payload['type']})
```
## Events
### `call.completed`
Quarterzip sends this event after a call finishes processing. The request is a JSON `POST` with `Content-Type: application/json`.
#### Example payload
```json theme={null}
{
"type": "call.completed",
"timestamp": "2026-08-03T10:44:12.318402+00:00", // ISO 8601, UTC, when we built the payload
"data": {
"call_id": "abc123",
"workspace_id": "ws_123",
"deployment_id": "dep_123",
"started_at": "2026-08-03T10:31:02.114000+00:00", // ISO 8601, always present
"ended_at": "2026-08-03T10:44:09.882000+00:00", // ISO 8601, or null
"is_test": false, // true for test calls
"is_coaching": false, // true for coaching calls
"is_phone": false, // true if call came over the phone
"is_sdk": true, // true if call came through SDK integration
"external": { // Metadata provied by via SDK/URL parameters
"user_id": "jandoe_123",
"email": "jane.doe@example.com",
"display_name": "Jane Doe",
},
"transcript": [
{ "speaker": "User", "text": "..." },
{ "speaker": "Agent", "text": "..." }
]
}
}
```
### `call.recording.ready`
Quarterzip sends an event after a call's recording is ready for download. The delivery of this event is not guaranteed; calls may not have recordings, or workspaces may have recordings disabled.
#### Example Payload
```json theme={null}
{
"type": "call.recording.ready",
"timestamp": "2026-08-03T10:44:12.318402+00:00", // ISO 8601, UTC, when we built the payload
"data": {
"call_id": "abc123",
"workspace_id": "ws_123",
"deployment_id": "dep_123",
"recording_url": "https://...", // A pre-signed expirying download URL
"recording_url_expires_at": false, // ISO 8601
}
}
```
### `call.rating.submitted`
Quarterzip sends an event after a caller submits a star rating for a call, and again if they change it. Ratings are optional and a caller may give one well after the call has ended, so this event is independent of call.completed and can arrive before or after it. Rating is an integer from 1 to 5.
Changing a rating produces a second delivery with its own webhook-id, so a correction is not a duplicate and will not be filtered out by idempotency checks. Deliveries for the same call are not guaranteed to arrive in order, so compare the envelope timestamp and ignore any rating older than one you have already applied.
#### Example Payload
```json theme={null}
{
"type": "call.rating.submitted",
"timestamp": "2026-08-03T10:44:12.318402+00:00", // ISO 8601, UTC, when we built the payload
"data": {
"call_id": "abc123",
"workspace_id": "ws_123",
"deployment_id": "dep_123",
"rating": 4 // An integer from 1 to 5
}
}
```
### `call.interpretation.ready`
Quarterzip sends this event once it has finished analysing a completed call. It summarises how the call went and lists each goal the user raised, along with how it came up, how it ended, and whether anything blocked it.
Defining a *successful* call is hard. At Quarterzip, we've formed strong opinions on how to evaluate a call with an agent. These definitions and terms, while they might make sense to you, may not be clear to an LLM tool ingesting this data. Below is a prompt of definitions to best help LLM tools to understand the data Quarterzip produces, so that an LLM can better answer your questions.
# Call interpretation definitions
Date: 29/09/2026
Audience: LLM & AI tools
Reference for reasoning about Quarterzip call interpretations: the technical meaning of every field and
every value it can take.
## Background
A Quarterzip call is a voice conversation between an end user and an AI product-expert agent embedded in
a customer's product. The user may share their screen.
An **agent** is a *deployment* (`deployment_id`). A **workspace** is the customer account that owns the
deployments.
If the transcript has no substantive user speech the LLM is skipped and the call is recorded as a non-starter.
## Call interpretation
One per call, delivered as the `data` of the `call.interpretation.ready` webhook.
| Field | Meaning |
| - | - |
| `call_id`, `workspace_id`, `deployment_id` | Identity. |
| `started_at` | When the call started. |
| `non_starter` | `true` when no two-way exchange occurred. A non-starter always has zero goals. |
| `non_starter_reason` | Why, when `non_starter` is true; otherwise `""`. See below. |
| `escalated` | `true` when at least one goal has a non-empty `escalation`. |
| `goals` | The user's goals, in order. Zero is a correct and common answer. |
| `call_note` | One plain past-tense sentence for someone scanning a list of calls: what the user came for, how it ended, and what stopped them. For a non-starter, why the call never began. |
### Non-starter reasons
| Value | Meaning |
| - | - |
| `nobody_spoke` | No usable user speech. Usually the microphone was never granted, or the call was abandoned during setup. |
| `open_mic` | The microphone captured speech not addressed to the agent — someone else in the room, a video playing, a meeting in progress. |
| `never_reached_product` | The user was present and trying, but the whole call was consumed by the call apparatus (microphone, permissions, screen sharing) and no product subject was reached. |
A call can be long, with many user turns, and still be a non-starter.
### Derived call flags
These are not in the payload; they are computed from it.
| Flag | Definition |
| - | - |
| **engaged** | `non_starter` is false. A two-way exchange happened. |
| **bounced** | Engaged, but zero goals — the user talked with the agent but no goal ever formed. |
### Goals
A goal is something the user wanted to solve with the product that persisted across the call.
| Field | Meaning |
| - | - |
| `goal` | What the user wanted, in one plain sentence. |
| `kind` | What would satisfy the user. See below. |
| `trigger` | What put the goal on the table. See below. |
| `provenance` | One or two sentences of evidence for `trigger`: who raised the goal, in response to what, and how the other party responded. |
| `outcome` | How the goal ended. See below. |
| `blocker` | Why it was blocked. Set only when `outcome` is `blocked`; otherwise `""`. |
| `escalation` | Whether the user wanted this goal taken to a human on the product's side. Independent of `outcome`. |
#### Kind
Judged by intent, not grammar.
| Value | Meaning | Example |
| - | - | - |
| `task` | Something must be changed or accomplished in the product. | "Where do I add a ticket type?" (they want it added) |
| `question` | An answer is needed; nothing in the product changes. | "What's this field for?" |
| `problem` | Something is behaving unexpectedly; needs explaining or repairing. | "Why is my French catalog full of another catalog's products?" |
#### Trigger
| Value | Meaning |
| - | - |
| `user_raised` | The user brought it up themselves, unprompted. |
| `agent_offered` | The agent proposed it from its own script, with no specific cue ("would you like a quick walkthrough?"). |
| `agent_noticed` | The agent saw something — on the user's screen (an error, a failing diagnostic, a half-finished form) or in a user's answer — and proposed acting on it. |
| `resumed` | Carried over from an earlier call, or from earlier in this one. |
#### Outcome
| Value | Meaning |
| - | - |
| `resolved` | The goal was achieved. |
| `deferred` | The user freely chose to stop — moved on, ran out of time, said they'd carry on alone. Nothing obstructed them. |
| `interrupted` | The call ended before the goal could conclude — cut off, screen share dropped, audio degraded. Nobody chose to stop and nothing blocked the goal; the channel failed. |
| `blocked` | Something stopped it; `blocker` names what. |
| `unclear` | Genuinely no signal. Should be rare. |
An outcome being `interrupted` or `blocked` is not inherently a negative outcome; the user has still been assisted by the agent.
If no product subject was ever reached, the call is a non-starter with zero goals — not a goal with an outcome.
#### Blocker
Set only on `blocked` goals.
| Value | Meaning |
| - | - |
| `product_defect` | Something in the product is broken: a button that does nothing, a save that errors, a flow that dead-ends. |
| `product_gap` | The product cannot do it. The capability is absent, not broken. |
| `agent_knowledge` | The agent did not know how. Usually self-reported, often ends in an escalation. |
| `agent_capability` | The agent is not permitted or able to do it by design ("I can't transfer you to a human"). |
| `external_dependency` | Something outside the product and outside the user's control: someone else administers the account, a third-party registrar, credentials they don't hold. |
| `commercial_gate` | A plan limit, quota or paywall. A pricing event, not a failure. |
| `unclear` | Cannot be told from the transcript. Preferred over guessing between `product_defect` and `external_dependency`. |
#### Escalation
Whether the user explictly wanted **this goal** taken to a human? Independent of `outcome`: a goal can
be `resolved` because the handoff happened, or `blocked` and escalated for the same reason.
| Value | Meaning |
| - | - |
| `completed` | The handoff happened in the call — a ticket lodged, a support chat opened, an email sent to support. |
| `attempted` | The user wanted it, but it did not land before the call ended. |
| `""` | No escalation. The common answer. |
The only negative signal is explict & complete `escalation`. A `blocked` outcome is not ideal, but the act of an agent helping & assisting the user is a positive signal.
It **is** an escalation when the user asks for a human/rep/account manager at the vendor, raises (or says
they will raise) a ticket or bug report with the vendor, contacts vendor support, or accepts the agent's
offer to pass it to the team.
It is **not** an escalation when the user takes it to their own people (a colleague, their admin, their IT
team — including copying an email the agent drafted for them), goes to a third party they deal with
themselves (registrar, hosting or mail provider), declines or ignores an offer of support, asks "can I
talk to a real person?" to test the agent, or simply gives up. Talking to the agent is not reaching
support.
#### Concepts
Every goal a user raises on a call is tagged with a **type**, an **origin**, and an **outcome**. Users can discuss several goals in one call, and each goal is assessed on its own. Three of the outcomes combine into the enablement rate.
#### Goal type
The type describes what the user is trying to do.
Type
Description
Task
The user is trying to accomplish an action in your product.
Question
The user has a question about something inside your product.
Problem
Something in your product is behaving unexpectedly and needs explaining or repairing.
#### Goal origin
The origin describes how the goal came up during the call.
Origin
Description
User raised
The user brought it up themselves, unprompted.
Agent offered
The agent proposed it without any specific cue.
Agent noticed
The agent saw something on the user's screen, such as a failing diagnostic, an error, or a half-finished form, and proposed acting on it.
Resumed
The goal was carried over from an earlier call, or from earlier in the same call.
#### Goal outcome
The outcome is Quarterzip's determination of how the goal ended.
Outcome
Description
Counts as enabled
Confirmed resolved
Using screen awareness, Quarterzip confirmed the goal was achieved.
Yes
Assisted
The agent helped the user, but Quarterzip could not confirm the goal was resolved. For example, the user was cut off mid-sentence or screen sharing dropped.
Yes
Deferred
The user chose to stop before the agent could resolve the goal, without facing a blocker. For example, the user moved on, ran out of time, or said they'd carry on alone.
Yes
Blocked
The user could not achieve the goal because something was preventing them, such as a product bug or gap, an external dependency, or a gap in the agent's knowledge.
No
Unclear
There was no signal from the user about whether the goal was achieved.
No
#### Enablement rate
A user is considered **enabled** when their goal's outcome is confirmed resolved, assisted, or deferred. The enablement rate is comparable to the assumed resolution rate reported by tools such as Intercom or Sierra.
When working with webhooks, construct the enablement rate by summing the three rates:
```latex theme={null}
Enablement rate = Confirmed resolved rate + Assisted rate + Deferred rate
```
**Note:** We are continually improving our assessment pipeline. Current confidence is 90% for confirmed resolved outcomes and 75%+ for the enablement rate.
#### Viewing outcomes in Quarterzip
Where
What you can do
Call insights dashboard
Track your agents live: engaged calls, unique users, engaged hours, enablement rate, CSAT, outcomes by agent, and the reasons goals were blocked.
Call log
Review individual calls. Each call lists the user's goals with their type, origin, and outcome, plus a description of what happened, the steps the agent took the user through, and direct quotes from the user.
#### Example Payload
```json theme={null}
{
"type": "call.interpretation.ready",
"timestamp": "2026-08-03T10:44:12.318402+00:00", // ISO 8601, UTC, when we built the payload
"data": {
"call_id": "abc123",
"workspace_id": "ws_123",
"deployment_id": "dep_123",
"started_at": "2026-08-03T10:34:12.6183401+00:00",
"non_starter": "false",
"non_starter_reason": "",
"escalated": "false",
"call_note": "User reported a...",
"goals": [
{
"goal": "Determine why...",
"kind": "problem",
"trigger": "user_raised",
"provenance": "The user opened by saying they wanted to solve...",
"outcome": "resolved",
"blocker": "",
"escalation": "",
}
],
}
}
```
## Troubleshooting
Symptom
Likely cause
Not receiving any events
Endpoint is not **Enabled**, or supported events are occurring.
Signature verification fails
Verifying against a re-serialized body instead of the raw bytes, or using an old secret after a rotation.
Can't save the URL
URL must be `https` and resolve to a public IP — private and localhost addresses are rejected.
Lost the signing secret
The secret is only shown once — rotate it from **Settings → Webhooks** to generate a new one.