> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apifycloud.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting & FAQ

> Symptoms, causes, and fixes for the most common Click to Call issues

Organised by **symptom** — search for what your users report, not what
you think the cause is. Each entry links to the relevant guide for the
full context.

## The widget doesn't appear on the page

<AccordionGroup>
  <Accordion title="Page has a Content Security Policy">
    Your CSP blocks the iframe. Add the widget origin to the relevant
    directives — see
    [Embedding — Content Security Policy](/guides/click-to-call/embedding#content-security-policy).
  </Accordion>

  <Accordion title="Ad blocker or privacy extension">
    Some blocklists flag third-party embedded widgets. Ask the visitor
    to allowlist your domain, or test with blockers disabled to
    confirm.
  </Accordion>

  <Accordion title="Wrong app ID">
    The app id in the iframe URL must match the one in the console. A
    typo surfaces an error screen inside the iframe. The app id is
    visible in the console under the app's settings.
  </Accordion>

  <Accordion title="Page is served over HTTP">
    WebRTC requires HTTPS. The page embedding the widget must also be
    served over HTTPS.
  </Accordion>

  <Accordion title="Parent site restricts iframes">
    Some sites set `X-Frame-Options` or a `frame-ancestors` CSP
    directive that prevents iframes from embedding anything. If you
    see a browser console error about framing being refused, review
    those headers on your own site (not ours).
  </Accordion>
</AccordionGroup>

## Widget loads but shows an error

<AccordionGroup>
  <Accordion title="No active VoIP provider configured">
    The app needs a VoIP provider activated in the console for the
    widget to initialise. Without it, the session-token request fails
    and the widget drops into the error state. Check the app's
    provider configuration.
  </Accordion>

  <Accordion title="Network error fetching the session token">
    The visitor's browser couldn't reach our runtime — typically a
    transient connectivity issue or a corporate proxy blocking the
    request. Retrying usually resolves it.
  </Accordion>

  <Accordion title="Invalid widget key">
    If the embed's widget key doesn't match what the runtime expects
    (for example, after a manual copy-paste error), the session token
    request is rejected and the widget enters the error state. Verify
    the widget key in the console matches what's embedded.
  </Accordion>
</AccordionGroup>

## Widget shows the "closed" state

<AccordionGroup>
  <Accordion title="Business hours closed">
    The profile's business hours evaluate to closed at the visitor's
    access time. Confirm the schedule and timezone in the console. See
    [Business hours](/guides/click-to-call/business-hours).
  </Accordion>

  <Accordion title="Initial closed state via URL">
    The widget accepts an internal flag that boots it directly into
    the closed state (useful for testing the closure UI). Check the
    URL used on the failing page for any unexpected parameters.
  </Accordion>
</AccordionGroup>

## Microphone permission fails

<AccordionGroup>
  <Accordion title="User previously denied and hasn't reset">
    Browsers cache the `denied` state indefinitely. The widget
    surfaces a "permission blocked" message with the steps to
    re-enable it. Pages can't programmatically re-prompt after a
    deny.
  </Accordion>

  <Accordion title="Page is not in a secure context">
    The microphone only works on `https://`. The widget surfaces a
    clear error message in this case.
  </Accordion>

  <Accordion title="Iframe missing `allow=microphone`">
    If you embedded the iframe manually, the attribute is required —
    see [Embedding](/guides/click-to-call/embedding).
  </Accordion>

  <Accordion title="Permissions Policy header blocks the origin">
    If your page is nested in another iframe, the outer container
    must delegate `microphone=(self "https://c2c.apifycloud.io")`.
    See [Embedding — Nested iframes](/guides/click-to-call/embedding#nested-iframes).
  </Accordion>

  <Accordion title="iOS — microphone blocked at the OS level">
    On iOS, `Settings → Safari → Microphone` has a global setting.
    If the user has set it to "Deny", page-level grants don't
    override it.
  </Accordion>
</AccordionGroup>

## Can't tap the call button (disabled)

<AccordionGroup>
  <Accordion title="Required pre-call form field is empty">
    If you configured required fields in the pre-call form, the call
    button stays disabled until all of them have a value. Hover the
    button for the "complete the required fields" tooltip.
  </Accordion>

  <Accordion title="Microphone permission not yet granted">
    The widget surfaces a mic banner before the call button is
    usable. The visitor must grant permission first.
  </Accordion>

  <Accordion title="Widget is still initialising">
    Briefly after page load, the widget fetches a session token. The
    call button is inactive during that window. If it stays inactive,
    see [Widget loads but shows an error](#widget-loads-but-shows-an-error).
  </Accordion>
</AccordionGroup>

## Call starts but doesn't reach the agent

<AccordionGroup>
  <Accordion title="No SIP destination configured for the app">
    If the app has no SIP destination (directly or via a default
    profile), the answer webhook replies with a spoken "no
    destination" message instead of connecting the call. Add at least
    one SIP destination in the console.
  </Accordion>

  <Accordion title="Routing to a destination that doesn't exist">
    Passing `?r=PROFILE_ID` for a profile that doesn't exist for this
    app silently falls back to the default. If the default is also
    missing, the call fails with a spoken error. Double-check the
    profile id.
  </Accordion>

  <Accordion title="SIP URI unreachable or misconfigured">
    The SIP endpoint you configured may be offline, behind a firewall,
    or incorrectly formatted. Test the URI from your contact centre's
    side to confirm it can receive calls.
  </Accordion>
</AccordionGroup>

## Call connects but no audio (one-way or no-way)

<AccordionGroup>
  <Accordion title="Autoplay policy blocked audio playback">
    Happens when the call is initiated without a user gesture. Always
    trigger calls from a direct tap or click.
  </Accordion>

  <Accordion title="System output device is silent or on a different sink">
    Ask the user to check system volume and the active output device
    (Bluetooth headset vs. speakers). The widget uses the OS default.
  </Accordion>

  <Accordion title="Strict corporate firewall">
    Some corporate networks block UDP, which degrades WebRTC audio.
    See [Network requirements](/guides/click-to-call/network-requirements).
  </Accordion>
</AccordionGroup>

## Calls cut off or drop unexpectedly

<AccordionGroup>
  <Accordion title="User switched networks mid-call (Wi-Fi ↔ cellular)">
    WebRTC sessions don't survive IP changes. This is a protocol
    limitation.
  </Accordion>

  <Accordion title="Packet loss spike">
    Check the [Network indicator](/guides/click-to-call/audio-quality)
    state at the time of the drop. Sustained `poor` → call fails.
  </Accordion>

  <Accordion title="Computer went to sleep">
    Obvious but common. Mobile devices locking the screen usually
    maintain the call; desktops sleeping don't.
  </Accordion>
</AccordionGroup>

## Calls sound bad (distortion, echo, jitter)

<AccordionGroup>
  <Accordion title="Bluetooth headset on the user side">
    BT headsets can add latency and introduce artefacts. Wired
    earbuds are usually cleaner.
  </Accordion>

  <Accordion title="Agent side hardware">
    A surprising share of "bad call" reports trace back to cheap USB
    headsets with variable mic gain. See
    [Audio quality — admin fixes](/guides/click-to-call/audio-quality#what-the-admin-can-do).
  </Accordion>

  <Accordion title="Network jitter">
    See the Network indicator state. Consistent amber/red is a
    network quality issue, not a widget bug.
  </Accordion>

  <Accordion title="Echo on the agent side">
    Agents not using a headset (speakers + mic open) get feedback.
    Require headsets for agents.
  </Accordion>
</AccordionGroup>

## Survey doesn't appear after the call

<AccordionGroup>
  <Accordion title="Survey disabled on the profile">
    Each profile has a Satisfaction Survey toggle. If it's off, the
    widget jumps from `ended` directly to `closed` and no survey
    renders.
  </Accordion>

  <Accordion title="No survey blocks designed in Call Studio">
    The survey only renders if the Call Studio design includes survey
    blocks on the `survey` page. An empty page means the widget also
    skips to `closed`.
  </Accordion>

  <Accordion title="Call went to the error state">
    If the call ends in the error state (media failure, no
    destination, etc.), the widget doesn't show a survey.
  </Accordion>
</AccordionGroup>

## A block I added in Call Studio doesn't appear in the widget

<AccordionGroup>
  <Accordion title="Block was added to a different state page">
    Call Studio has a separate page per call state (idle, calling,
    ended, survey, survey\_submitted, closed). A block you added on
    the `ended` page is only rendered when the call has ended — not
    when you open the widget from idle.
  </Accordion>

  <Accordion title="Design wasn't saved">
    Make sure the save action completed in Call Studio. Reload the
    editor to confirm the change persists.
  </Accordion>

  <Accordion title="Survey post-submit page is hiding it">
    If the widget design has a dedicated `survey_submitted` page,
    after the visitor submits the survey the widget switches to that
    page. Blocks on the regular `survey` page aren't rendered there.
  </Accordion>
</AccordionGroup>

## Webhooks don't arrive

<AccordionGroup>
  <Accordion title="Integration is paused by the circuit breaker">
    After 20 consecutive failures the integration is deactivated
    automatically. In the console, its status shows **Paused** — fix
    the underlying issue, then reactivate it. See
    [Integrations — Circuit breaker](/guides/click-to-call/integrations#circuit-breaker).
  </Accordion>

  <Accordion title="Event isn't in the integration's allowlist">
    Easy to miss. Open the integration editor and confirm the event
    name is selected.
  </Accordion>

  <Accordion title="Endpoint returning 4xx">
    Any `4xx` response stops retries immediately. Fix the endpoint to
    accept the payload or correct the integration config.
  </Accordion>

  <Accordion title="Request body too large">
    64 KB cap. Widget events are well under this — if you're hitting
    it, the URL context is likely being abused.
  </Accordion>
</AccordionGroup>

## Events arrive with missing `context`

<AccordionGroup>
  <Accordion title="Context param key was dropped by validation">
    Keys must match `^[a-zA-Z0-9_\-.]{1,64}$`. Names with spaces,
    non-ASCII characters, or special symbols are dropped silently. See
    [Embedding — URL context](/guides/click-to-call/embedding#url-context).
  </Accordion>

  <Accordion title="Total context exceeded 2 KB">
    Later keys are dropped once the total budget is hit. Keep values
    short.
  </Accordion>

  <Accordion title="Value exceeded 512 bytes">
    Per-value cap — individual oversized values get truncated.
  </Accordion>

  <Accordion title="Using a reserved key name">
    `session`, `preview`, `configId`, `r`, `lang` are reserved for
    the runtime and never exposed as context.
  </Accordion>
</AccordionGroup>

## Custom code doesn't fire

<AccordionGroup>
  <Accordion title="Script is in `head` but needs the DOM">
    Move it to **Body end** in Call Studio, or wrap it in a
    `DOMContentLoaded` listener.
  </Accordion>

  <Accordion title="External script blocked">
    Check the iframe's dev tools Network tab for blocked requests —
    ad blockers, privacy extensions, or the user's corporate proxy
    can block third-party scripts.
  </Accordion>

  <Accordion title="Trying to access the embedding page's cookies">
    The sandbox has an opaque origin. See
    [Custom code — Limitations](/guides/click-to-call/custom-code#limitations-to-keep-in-mind).
  </Accordion>

  <Accordion title="Error thrown in a handler">
    Handler errors are swallowed to protect other handlers. Check
    the sandbox iframe's console directly.
  </Accordion>
</AccordionGroup>

## Parent-frame events not received

When embedded in an iframe, the widget can send `postMessage` events
to the parent page. If your parent isn't receiving them:

<AccordionGroup>
  <Accordion title="Parent origin not in the allowlist">
    The widget only emits to origins explicitly authorised for your
    app. Without an allowlist, it emits nothing — by design.
  </Accordion>

  <Accordion title="Firefox — no handshake sent">
    Firefox doesn't expose the parent's origin automatically. The
    parent page must send a `c2c:handshake` message after the iframe
    loads. See
    [Embedding — Firefox handshake](/guides/click-to-call/embedding#firefox-handshake).
  </Accordion>

  <Accordion title="Using the direct-link mode instead of an iframe">
    When visitors open the widget via its hosted URL directly (not
    iframed), there is no parent page to receive events. Use
    integrations to forward events server-side instead.
  </Accordion>
</AccordionGroup>

## Preview mode differs from production

The widget's preview mode (used inside Call Studio) is deliberately
isolated from real call flows:

* **No audio session is established** — the VoIP SDK doesn't
  initialise, and tapping the call button does not place a real
  call.
* **Custom code is not executed** — the sandbox iframe isn't created
  in preview, so injected analytics / scripts won't run.
* **Events aren't forwarded to integrations** — the preview never
  hits your webhook endpoints.
* **A state switcher is visible** — an extra UI control lets
  designers navigate between widget states manually.

If your preview and production behave differently, it's almost
always one of these by design.

## Getting support

If none of the above matches, open a support ticket with:

* App ID
* Browser and OS versions
* Timestamp of the issue (include timezone)
* Steps to reproduce

## What's next

<CardGroup cols={2}>
  <Card title="Network requirements" href="/guides/click-to-call/network-requirements">
    What your network needs for reliable calls.
  </Card>

  <Card title="Security" href="/guides/click-to-call/security">
    Sandbox, encryption, and data boundaries.
  </Card>
</CardGroup>
