> ## 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.

# Integrations (webhooks)

> Forward widget events to your webhooks, analytics, and third-party destinations

Integrations let you forward widget events to any HTTP destination — your
own server, an analytics platform, a CRM, a Slack channel — without
writing any client-side code. Events are sent to our server using a
keepalive request and dispatched from there to your integrations with
retries and a circuit breaker — so they survive the visitor closing the
page in most cases.

## How it works

When the widget emits an event, ApifyCloud fans it out to each active
integration configured on the app:

```
Widget event
    │
    ▼
ApifyCloud runtime
    │
    ├──▶ Integration 1 (your analytics endpoint)
    ├──▶ Integration 2 (your team chat webhook)
    └──▶ Integration 3 (your CRM)
```

Each integration:

* Has its own URL, headers, and authentication
* Is filtered to the events you want
* Retries on failure with exponential backoff and jitter
* Trips a circuit breaker after sustained failures
* Encrypts its secrets at rest

## Creating an integration

In the console under **Call Studio → Integrations**:

<Steps>
  <Step title="Set the destination URL">
    HTTPS only. HTTP URLs are rejected to prevent plaintext leakage of
    PII.
  </Step>

  <Step title="Pick the events to forward">
    By default no events are selected. Choose specifically which ones
    you want delivered — most integrations only need a subset.
  </Step>

  <Step title="Add auth headers or secrets">
    Static headers (e.g. `Authorization: Bearer ...`) go in the headers
    list. Values stored under the **Secrets** section are encrypted at
    rest and can be referenced from URLs or headers.
  </Step>
</Steps>

<Info>
  You can have up to **3 active integrations per app**. This cap is
  deliberate — more integrations means more latency on the shared
  dispatch loop. If you need more, contact support.
</Info>

## Request format

We POST a JSON body with the event envelope:

```json theme={null}
{
  "name": "call_ended",
  "data": {
    "duration": 187,
    "reason": "user_hangup",
    "context": { "orderId": "A-12345" }
  },
  "timestamp": "2026-04-19T14:08:29.446Z",
  "app": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "My Support Line"
  }
}
```

The body is UTF-8 JSON with `Content-Type: application/json`.

## Securing your endpoint

Add an `Authorization: Bearer <token>` header (or any other auth
header you prefer) when creating the integration. Your endpoint rejects
any request without the matching token.

## Secrets

Secrets are values you don't want to see again after saving — API keys,
bearer tokens, signing keys. Store them in the **Secrets** section of
the integration:

* Values are encrypted at rest with AES-256-GCM.
* Reveal is one-time-only, with a full audit log (who revealed what,
  when, from which IP).
* Referenced in URLs and headers using the `{secret.keyName}` syntax.
* Reveals are rate-limited (10 per hour per integration) to slow down
  an attacker with compromised console access.

Rotating a secret: edit the integration, find the secret in the list,
replace its value, and save. The new value takes effect on the next
delivery.

## Delivery reliability

### Timeouts

Each delivery has a configurable timeout, up to **10 seconds**. If your
endpoint doesn't respond within the configured window, it counts as a
failure.

### Retries

On transient failure (5xx response, connection reset, timeout) we
retry with exponential backoff and ±20 % jitter. After up to
**3 attempts** the delivery is marked failed.

### Short-circuit on 4xx

Any `4xx` response stops retries immediately. A `4xx` means the
request was rejected by your endpoint and retrying won't fix it —
fix the integration config or your endpoint.

### Circuit breaker

After **20 consecutive failures** across any events, the integration
is automatically deactivated and stops dispatching. Fix the issue
(wrong URL, expired token, endpoint down) and reactivate the
integration from the console.

This protects you from:

* Burning retries against a dead endpoint
* Accumulating a retry backlog that delivers out-of-order
* Triggering rate limits on third-party services

### Body size cap

Request bodies are capped at **64 KB**. Events are well under this —
it's a defensive limit only.

## Debugging a failing integration

<AccordionGroup>
  <Accordion title="Check the integration status in the console">
    The integration list shows a badge — **Active** or **Paused** — and
    a success-rate percentage. If the integration is **Paused**, the
    circuit breaker tripped and you need to reactivate it after fixing
    the cause. If the success rate is low, your endpoint is rejecting
    deliveries.
  </Accordion>

  <Accordion title="Check your endpoint's access logs">
    Did the request reach you? If yes, check the response status — that's
    the value our dispatcher sees. If no, the issue is DNS or a firewall
    on your side.
  </Accordion>

  <Accordion title="Review event selection">
    If live events don't arrive, check the event allowlist on the
    integration. It's easy to enable it for `call_started` but forget
    `call_ended`.
  </Accordion>

  <Accordion title="Confirm the endpoint accepts the payload shape">
    See [Request format](#request-format) above and compare against
    what your endpoint expects.
  </Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
  <Card title="Events" href="/guides/click-to-call/events">
    Catalogue of events you can subscribe to.
  </Card>

  <Card title="Security" href="/guides/click-to-call/security">
    How integration secrets and event data are protected.
  </Card>

  <Card title="Troubleshooting" href="/guides/click-to-call/troubleshooting">
    When deliveries fail and you can't tell why.
  </Card>
</CardGroup>
