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

# Zapier integration

> Connect Zapier to 8call using OAuth2 and REST Hooks. This guide documents all endpoints involved in the Zapier app and the call.completed trigger event end-to-end.

<Info>
  This guide shows how to connect Zapier to 8call using OAuth2 Authorization Code flow and a single REST Hook trigger, <strong>call.completed</strong>. It covers all related endpoints, required headers, payload formats, and verification steps needed for Zapier App Review.
</Info>

### Overview

* **Authentication**: OAuth2 Authorization Code with Bearer tokens.
* **Trigger (REST Hook)**: <strong>call.completed</strong> (the only event documented and exposed to Zapier).
* **Perform List**: `GET {API_URL}/calls/recent` provides call samples to configure the trigger.
* **Webhooks**: Zapier subscribes using `POST {API_URL}/webhooks/subscribe` and unsubscribes with `DELETE {API_URL}/webhooks/subscribe`.
* **Security**: Optional shared secret forwarded as `X-Signature` header in webhook deliveries.

<CardGroup cols={2}>
  <Card title="Authorize URL" icon="key">
    `GET {API_URL}/oauth/authorize`
  </Card>

  <Card title="Token URL" icon="lock">
    `POST {API_URL}/oauth/token`
  </Card>
</CardGroup>

<CardGroup cols={2}>
  <Card title="Userinfo (Test URL)" icon="user">
    `GET {API_URL}/oauth/userinfo`
  </Card>

  <Card title="Webhook Subscribe" icon="bolt">
    `POST {API_URL}/webhooks/subscribe`
  </Card>
</CardGroup>

<CardGroup cols={2}>
  <Card title="Webhook Unsubscribe" icon="trash">
    `DELETE {API_URL}/webhooks/subscribe`
  </Card>

  <Card title="Perform List" icon="list-check">
    `GET {API_URL}/calls/recent`
  </Card>
</CardGroup>

<Note>
  Replace `{API_URL}` with your environment’s base URL (e.g. `https://api.8call.co`).
</Note>

### Authentication and security

All Zapier → 8call requests are protected by a combination of:

* An OAuth **client ID / client secret** pair

* An OAuth **bearer access token**

* Per-request **client headers** that identify the Zapier app

* **Client credentials**: When you create the Zapier app, you configure an OAuth client in 8call that issues a `client_id`, `client_secret`, and `redirect_uri`. These values are also made available to your Zap to send with each API call.

* **Token exchange**: Zapier exchanges the authorization `code` at `POST {API_URL}/oauth/token` using the `client_id` and `client_secret`. Only registered clients can receive access tokens.

* **Client headers**: Every Zapier request to 8call must include the client identity:

  <ParamField header="x-client-id" type="string" required>
    Your 8call Zapier OAuth client ID.
  </ParamField>

  <ParamField header="x-client-secret" type="string" required>
    Your 8call Zapier OAuth client secret.
  </ParamField>

* **Bearer protection**: Every authenticated request from Zapier to 8call must also include:

  <ParamField header="Authorization" type="string" required>
    `Bearer {{bundle.authData.access_token}}`
  </ParamField>

  On the server, 8call validates the bearer token and checks that `x-client-id` / `x-client-secret` match the configured Zapier client (`ZAPIER_CLIENT_ID`, `ZAPIER_CLIENT_SECRET`) before executing the request.

* **Protected endpoints**: Webhook subscribe/unsubscribe and perform list endpoints (`/webhooks/subscribe`, `/calls/recent`) all require these headers (`Authorization`, `x-client-id`, `x-client-secret`). Invalid or missing credentials result in `401 Unauthorized` responses.

### Video walkthrough

<Frame caption="End-to-end Zapier + 8call setup walkthrough">
  <video controls className="w-full aspect-video rounded-xl" src="https://mintcdn.com/8call/RVc7GPvVDZvCZUvl/videos/zapier.mp4?fit=max&auto=format&n=RVc7GPvVDZvCZUvl&q=85&s=a542c431bb819d023bf9f73815440d9b" data-path="videos/zapier.mp4" />
</Frame>

### Prerequisites

* A Zapier developer account with access to the Zapier Platform UI.
* An 8call account and an OAuth client configured.

<Steps>
  <Step title="Create an OAuth2 app in Zapier Platform UI">
    Set the following in your Zapier app:

    * **Authorize URL**: `GET {API_URL}/oauth/authorize`
    * **Token URL**: `POST {API_URL}/oauth/token`
    * **Refresh Token URL**: same Token URL with `grant_type=refresh_token`
    * **Test URL**: `GET {API_URL}/oauth/userinfo`
    * **Connection Label**: use `email` from the Test URL response

    <ParamField header="Authorization" type="string" required>
      Send `Bearer {{bundle.authData.access_token}}` on all authenticated requests.
    </ParamField>

    <Check>
      Use the “Test” button in Zapier to verify the user profile request succeeds.
    </Check>
  </Step>

  <Step title="Configure OAuth scopes">
    Supported scopes in 8call (request only what you need):

    * calls:read, calls:write
    * contacts:read, contacts:write
    * leads:read, leads:write
    * audiences:write

    <Tip>
      Least-privilege is recommended. Request the minimal set of scopes required for your Zap.
    </Tip>
  </Step>

  <Step title="Set up REST Hook triggers">
    Each trigger uses a subscribe, unsubscribe, and perform list endpoint. All webhook requests from Zapier must include:

    <ParamField header="Authorization" type="string" required>
      `Bearer {{bundle.authData.access_token}}` (OAuth2 bearer token)
    </ParamField>

    <ParamField header="Content-Type" type="string">
      `application/json`
    </ParamField>

    For the Zapier public app, 8call documents and exposes <strong>one</strong> REST Hook trigger:

    * <strong>call.completed</strong> – fired when a call handled by 8call has finished and a summary is available.
  </Step>
</Steps>

### Subscribe to a webhook (`POST /webhooks/subscribe`)

You configure Zapier’s <strong>Subscribe URL</strong> to call `POST {API_URL}/webhooks/subscribe` whenever a Zap is turned on.

#### Request

<ParamField body="hookUrl" type="string" required>
  Public HTTPS URL Zapier provides as your target URL.
</ParamField>

<ParamField body="event" type="string">
  Must be `"call.completed"`. If Zapier hardcodes the event, you can omit this field in dev and 8call will default it to `call.completed`.
</ParamField>

<ParamField body="secret" type="string">
  Optional shared secret. If provided, deliveries include header `X-Signature: {secret}`.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST '{API_URL}/webhooks/subscribe' \
    -H 'x-client-id: YOUR_CLIENT_ID' \
    -H 'x-client-secret: YOUR_CLIENT_SECRET' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "hookUrl": "https://hooks.zapier.com/hooks/standard/123/abc/",
      "event": "call.completed",
      "secret": "zapier_shared_secret_123"
    }'
  ```
</RequestExample>

#### Response

On success 8call returns the created subscription:

<ResponseExample>
  ```json Success theme={null}
  {
    "id": "b2b0f21a-7ad4-48b1-8f4b-14e2b1b5f2a2",
    "event": "call.completed",
    "callback_url": "https://hooks.zapier.com/hooks/standard/123/abc/"
  }
  ```
</ResponseExample>

<CodeGroup>
  ```javascript Node.js theme={null}
  // Subscribe using Node (fetch)
  const res = await fetch('{API_URL}/webhooks/subscribe', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      hookUrl: 'https://hooks.zapier.com/hooks/standard/123/abc/',
      event: 'call.completed',
      secret: 'zapier_shared_secret_123'
    })
  });
  if (!res.ok) throw new Error(await res.text());
  const data = await res.json();
  console.log(data);
  ```
</CodeGroup>

### Unsubscribe from a webhook (`DELETE /webhooks/subscribe`)

You configure Zapier’s <strong>Unsubscribe URL</strong> to call `DELETE {API_URL}/webhooks/subscribe` when a Zap is turned off.

#### Request

<ParamField body="hookUrl" type="string" required>
  The exact target URL to remove.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X DELETE '{API_URL}/webhooks/subscribe' \
    -H 'x-client-id: YOUR_CLIENT_ID' \
    -H 'x-client-secret: YOUR_CLIENT_SECRET' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "hookUrl": "https://hooks.zapier.com/hooks/standard/123/abc/"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json Success theme={null}
  {
    "success": true
  }
  ```
</ResponseExample>

### Perform List – sample calls (`GET /calls/recent`)

Zapier uses the <strong>Perform List</strong> URL to fetch a small array of sample items when a user creates the Zap. For the `call.completed` trigger, configure the Perform List URL to `GET {API_URL}/calls/recent`.

<Tabs>
  <Tab title="Recent calls">
    <RequestExample>
      ```bash cURL theme={null}
      curl -X GET '{API_URL}/calls/recent' \
        -H 'x-client-id: YOUR_CLIENT_ID' \
        -H 'x-client-secret: YOUR_CLIENT_SECRET' \
        -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
      ```
    </RequestExample>

    <ResponseExample>
      ```json Success theme={null}
      [
        {
          "id": "c9d0a1a0-4c47-4e9f-9b9f-2c6e4a9dbe00",
          "created_at": "2025-01-15T10:30:00Z",
          "summary": "Answered, discussed pricing, requested follow-up."
        }
      ]
      ```
    </ResponseExample>

    <CodeGroup>
      ```javascript Node.js theme={null}
      // Fetch recent calls for use in a Zapier Perform List
      const res = await fetch(`${process.env.API_URL}/calls/recent`, {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
        },
      });
      if (!res.ok) {
        throw new Error(`Failed to fetch recent calls: ${res.status} ${await res.text()}`);
      }
      const calls = await res.json();
      console.log('Recent calls:', calls);
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Webhook deliveries

8call will send an HTTP POST to your subscribed `callback_url` with:

* Header `X-Event: {event}`
* Header `X-Signature: {secret}` when a secret was provided at subscription time
* JSON body containing the event payload

<Note>
  `X-Signature` is a simple shared-secret echo. It is <strong>not</strong> an HMAC. Verify equality on receipt if you supplied a secret during subscription.
</Note>

#### Example delivery: `call.completed`

The HTTP POST body for a `call.completed` webhook is a single JSON object representing the completed call:

<ResponseExample>
  ```json Success theme={null}
  {
    "id": "c9d0a1a0-4c47-4e9f-9b9f-2c6e4a9dbe00",
    "organization_id": "b1a2e3f4-5678-90ab-cdef-1234567890ab",
    "created_at": "2025-01-15T10:30:00Z",
    "summary": "Answered, discussed pricing, requested follow-up.",
    "duration_seconds": 245,
    "agent_id": "b3f9a8b6-7c21-4e5b-8d3a-9e1c2f4a5b6c"
  }
  ```
</ResponseExample>

<CodeGroup>
  ```javascript Verify signature (Node.js) theme={null}
  import express from 'express';
  const app = express();
  app.use(express.json());

  app.post('/zapier-target', (req, res) => {
    const event = req.header('X-Event');
    const signature = req.header('X-Signature');
    if (process.env.ZAPIER_SHARED_SECRET && signature !== process.env.ZAPIER_SHARED_SECRET) {
      return res.status(401).send('Invalid signature');
    }
    console.log('Event:', event, 'Payload:', req.body);
    res.status(200).send('ok');
  });
  ```

  ```javascript Process payload (Node.js) theme={null}
  // Example handler that logs and routes completed calls into your own system
  app.post('/zapier-call-completed', (req, res) => {
    const event = req.header('X-Event');
    if (event !== 'call.completed') {
      return res.status(400).send('Unsupported event');
    }

    const call = req.body;
    // Example: save to your CRM or database
    // await crm.createCall({
    //   externalId: call.id,
    //   summary: call.summary,
    //   durationSeconds: call.duration_seconds,
    //   happenedAt: call.created_at,
    // });
    console.log('Call completed:', call);
    res.status(200).send('ok');
  });
  ```
</CodeGroup>

### OAuth endpoints

<AccordionGroup>
  <Accordion title="Authorize URL">
    `GET {API_URL}/oauth/authorize`

    * Standard OAuth2 Authorization Code flow.
    * Redirects back using your registered `redirect_uri` with `code` parameter.
  </Accordion>

  <Accordion title="Token URL (+ Refresh)">
    `POST {API_URL}/oauth/token`

    * Exchange the authorization `code` for `access_token` and `refresh_token`.
    * For refresh: send the same endpoint with `grant_type=refresh_token`.
  </Accordion>

  <Accordion title="Userinfo (Test URL)">
    `GET {API_URL}/oauth/userinfo`

    * Returns the authenticated user profile used for connection label.
  </Accordion>
</AccordionGroup>

### Troubleshooting

* **401 Unauthorized**
  * Ensure `Authorization: Bearer {{bundle.authData.access_token}}` is present on all requests.
  * Verify your token is not expired; refresh if necessary.
  * Check that the token maps to an active user and organization in 8call.

* **400 Bad Request (subscribe/unsubscribe)**
  * Confirm `hookUrl` is a valid public HTTPS URL.
  * Ensure `event` is one of the supported values.

* **Webhooks not arriving**
  * Verify your target URL is reachable from the public internet.
  * If you set a `secret`, confirm you’re checking `X-Signature` exactly.
  * Check Zapier’s Zap history to confirm the subscribe call succeeded.

* **Empty Perform List**
  * You may not have recent data in your environment. Create a test call in 8call, then retry `GET /calls/recent`.

<Check>
  If you can successfully subscribe, see deliveries in your Zap’s history, and fetch Perform List samples, your Zapier integration with 8call is set up correctly.
</Check>
