# Guide to Circleback Webhooks: Features and Best Practices

Circleback webhooks send your meeting data out of Circleback once a meeting has been processed: the notes, action items, transcript and insights that Circleback's AI produces. If your team records meetings with Circleback, a webhook is how those outcomes reach a CRM, a project tracker, a knowledge base or your own product.

This guide covers how Circleback webhooks are set up and signed, what each request contains, and the practices that keep a Circleback receiver reliable.

## What are Circleback webhooks?

In Circleback, a webhook is a step in an automation. The automation's conditions decide which meetings it runs after, and its **Send webhook request** step names the endpoint and the meeting outcomes to include. Each matching meeting becomes one `POST` with a JSON body describing the meeting.

There are no event types. Every request is the same kind of thing, a meeting, and the body has no field that names an event. Which fields are filled depends on the outcomes you chose in the step.

Each request carries an `x-signature` header: the bare hex HMAC-SHA256 of the raw body, keyed with the signing secret Circleback gives you for that automation step.

## Circleback webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Automations > New automation**, with a **Send webhook request** step |
| Filtering | The automation's conditions decide which meetings are sent |
| Content | The meeting outcomes you select in the step |
| Event type | None. Each request is one meeting |
| Signature header | `x-signature` |
| Scheme | HMAC-SHA256, hex digest, no prefix |
| Signed content | The raw request body only |
| Secret | One per automation webhook step |
| Testing | A test request for the most recent meeting, sent from the step |
| Backfill | Select past meetings and run the automation from the **Actions** menu |
| Retries | Not documented |
| Source IPs | Not published |

## What the payload contains

Every request is a meeting object:

| Field | Contents |
| --- | --- |
| `id` | The Circleback meeting ID. `https://circleback.ai/meetings/${id}` links to it |
| `name`, `createdAt`, `duration` | The meeting's name, creation time (ISO string) and length in seconds |
| `url` | The video call link, or `null` |
| `recordingUrl` | The recording file, valid for 24 hours, and only present if saving recordings is turned on |
| `tags`, `icalUid` | Tag names, and the calendar event's ID or `null` |
| `attendees` | `name` and `email` for each attendee, either of which can be `null` |
| `notes` | The meeting notes in Markdown |
| `actionItems` | `id`, `title`, `description`, `assignee` (or `null`) and `status` (`PENDING` or `DONE`) |
| `transcript` | Segments with `speaker`, `text` and `timestamp` in seconds |
| `insights` | An object keyed by insight name, each an array of `insight`, `speaker` and `timestamp` |

A trimmed version of Circleback's example:

```json
{
  "id": "WRweb12iCBimU6Vo8cD7z",
  "name": "Event Venue Review",
  "createdAt": "2023-07-25T20:30:57.926Z",
  "duration": 1306.09,
  "url": "https://meet.google.com/abc-defgh-xyz",
  "recordingUrl": null,
  "tags": ["Events"],
  "icalUid": "1a2b3c@google.com",
  "attendees": [
    { "name": "John Appleseed", "email": "john@example.com" }
  ],
  "actionItems": [
    {
      "id": 253,
      "title": "Review and finalize venue options",
      "description": "Review pricing and availability of venues and pick a venue for the event.",
      "assignee": null,
      "status": "PENDING"
    }
  ],
  "transcript": [
    { "speaker": "John Appleseed", "text": "Hey, how's it going?", "timestamp": 4.56 }
  ]
}

```

An `insight` is a string, or an object when the insight has custom fields, so check its type before using it.

## Setting up Circleback webhooks

1. In Circleback, open **Automations** and select **New automation**.
2. Add conditions that limit which meetings the automation runs after, then select **Next**.
3. Select **Send webhook request**.
4. Enter the endpoint URL and choose the meeting outcomes to include.
5. Copy the signing secret and store it server-side, for example as `CIRCLEBACK_WEBHOOK_SECRET`.
6. Optionally, send a test request for the most recent meeting.
7. Select **Done**, name the automation and **Save**.

Each automation step has its own secret. If you run several automations, give each one its own endpoint or its own Hookdeck source, so every receiver checks requests against the right secret.

To send meetings that happened before you set up the automation, select them in Circleback's meeting list and choose the automation from the **Actions** menu.

## Securing Circleback webhooks

Compute an HMAC-SHA256 of the raw body with the signing secret, hex-encode it, and compare it with `x-signature` in constant time. Circleback calls this step optional, but it is the only way to tell that a request came from Circleback.

```javascript
const crypto = require('crypto');
const express = require('express');

function verifyCirclebackSignature(rawBody, header, secret) {
  if (typeof header !== 'string' || !secret) return false;
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex'),
    'utf8'
  );
  const received = Buffer.from(header.trim().toLowerCase(), 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/circleback', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.CIRCLEBACK_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyCirclebackSignature(req.body, req.get('x-signature'), secret)) {
    return res.sendStatus(401);
  }

  const meeting = JSON.parse(req.body.toString('utf8'));
  // Upsert on meeting.id, copy the recording if you need it, then process asynchronously.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hashlib
import hmac

def verify_circleback_signature(raw_body: bytes, header, secret: str) -> bool:
    if not header or not secret:
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header.strip().lower().encode("utf-8"), expected.encode("utf-8"))

```

Circleback's own sample mounts `express.raw({ type: 'application/json' })`. Using `*/*` keeps the body raw whatever content type arrives.

> **Make Circleback webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies the `x-signature` header, stores every meeting export, and delivers it to your services with retries and replay you control.

## Circleback webhook limitations and pain points

### No documented retries

**The Problem:** A meeting sent while your endpoint is down may never arrive.

**Why It Happens:** Circleback documents no timeout, no success code and no retry policy, so you can't count on a failed request being sent again.

**Workarounds:**

* Keep the endpoint highly available and return `200` quickly.
* After an outage, select the affected meetings and run the automation again from the **Actions** menu.

**How Hookdeck Can Help:** Hookdeck accepts and stores each request as it arrives, then delivers it to your service with [automatic retries](/docs/retries), so your own downtime delays meetings instead of losing them.

### The recording link expires

**The Problem:** The recording can't be downloaded when you get round to processing the meeting.

**Why It Happens:** `recordingUrl` is valid for 24 hours. A queue backlog, a retry or a replay of an older request can carry a link that has already expired.

**Workarounds:**

* Copy the recording to your own storage soon after the request arrives.
* Treat a failed download as expected for older requests, not as an error to retry forever.

**How Hookdeck Can Help:** Hookdeck delivers each request as soon as your service can take it, and [issues](/docs/issues) tell you when deliveries are failing so a backlog doesn't build up unnoticed.

### No event type and no timestamp

**The Problem:** You can't tell a first export from a repeat, and a captured request still verifies if sent again.

**Why It Happens:** The body is the meeting and nothing else. Only the body is signed, and no timestamp or delivery ID is sent. Re-running an automation on past meetings sends them again.

**Workarounds:**

* Upsert on the meeting `id`, so a repeat updates rather than duplicates.
* Keep the endpoint on HTTPS.

**How Hookdeck Can Help:** Hookdeck's [deduplication](/docs/deduplication) can drop repeated requests with the same meeting `id`.

## Best practices

### Verify against the raw body

The signature covers the exact bytes Circleback sent. Use `express.raw()` in Express, `await request.text()` in a Next.js route handler and `await request.body()` in FastAPI, and parse only after the check passes.

### Include only the outcomes you use

Transcripts make requests large. If you only need notes and action items, leave the transcript out of the step.

### Guard against nulls

`url`, `recordingUrl`, `icalUid`, attendee names and emails, and action item assignees can all be `null`. Use optional access throughout.

### Handle sensitive data carefully

Transcripts and notes can contain anything said in a meeting. Keep the endpoint on HTTPS and restrict who can read stored requests.

### Acknowledge fast and process asynchronously

Verify, store the meeting, return `200`, and do the work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks) and our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Conclusion

Circleback sends one signed `POST` per meeting from an automation step, with an `x-signature` header holding the bare hex HMAC-SHA256 of the raw body. Verify it with that step's secret, upsert on the meeting `id`, copy recordings within 24 hours, and plan for missed requests, because Circleback documents no retries.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, checks every Circleback signature, and holds each meeting until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Circleback webhooks reliably in minutes.