Agent skill
Circleback Webhooks Skill
Receive and verify Circleback (circleback.ai AI meeting notes) automation webhooks. Use when setting up a Circleback webhook handler, debugging x-signature verification (HMAC-SHA256 hex of the raw body), or processing the meeting export payload with notes, action items, attendees, transcript and insights.
Install this skill
npx skills add hookdeck/webhook-skills --skill circleback-webhooks
Circleback is an AI meeting notetaker. A Send webhook request step in a Circleback automation POSTs each matching meeting (notes, action items, attendees, and optionally transcript and insights) as JSON to your endpoint.
When to Use This Skill
- How do I receive Circleback webhooks after a meeting?
- How do I verify the Circleback
x-signatureheader? - How do I get Circleback meeting notes, action items or transcript via webhook?
- Why is my Circleback webhook signature verification failing?
- How do I export past Circleback meetings to my own endpoint?
Verification (core)
Circleback sends x-signature: the bare lowercase hex HMAC-SHA256 of the raw request body, keyed with the signing secret shown in the automation's webhook step (used as a UTF-8 string). There is no timestamp and no prefix. Check the header is exactly 64 hex characters before decoding (Buffer.from(x, 'hex') silently truncates invalid hex), then compare in constant time:
const crypto = require('crypto');
function verifyCirclebackSignature(rawBody, signature, secret) {
if (typeof signature !== 'string' || !/^[0-9a-f]{64}$/i.test(signature)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return crypto.timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}
Python (hmac.compare_digest on bytes, so a non-ASCII header gives 401, not 500):
import hashlib, hmac, re
def verify_circleback_signature(raw_body: bytes, signature: str | None, secret: str) -> bool:
if not signature or not re.fullmatch(r"[0-9a-fA-F]{64}", signature):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest().encode()
return hmac.compare_digest(expected, signature.lower().encode())
Read the raw body (express.raw({ type: '*/*' }), request.text(), await request.body()), not re-serialized JSON. Respond 401 on a bad signature.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Webhook Payload
There are no event types and no event-name field: every delivery is one meeting export, sent when an automation matches a meeting (or when you run the automation on past meetings). The fields included follow the automation's settings.
| Field | Notes |
|---|---|
id | Circleback meeting ID; stable, so use it for idempotency. Link: https://circleback.ai/meetings/${id} |
name, createdAt, duration | Meeting name, ISO timestamp, seconds |
url, recordingUrl | Virtual meeting URL; recording URL (valid 24 hours, only if recordings are saved), both nullable |
tags, icalUid | Tag names; calendar event UID (nullable) |
attendees | [{ name, email }] (both nullable) |
notes | Markdown meeting notes |
actionItems | [{ id, title, description, assignee: { name, email } | null, status: 'PENDING' | 'DONE' }] |
transcript | [{ speaker, text, timestamp }] (seconds) |
insights | { [insightName]: [{ insight, speaker, timestamp }] } |
Important Headers
| Header | Description |
|---|---|
x-signature | Hex HMAC-SHA256 of the raw body (optional on Circleback's side, recommended) |
Environment Variables
CIRCLEBACK_SIGNING_SECRET=your_signing_secret # Shown in the automation's webhook step
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 circleback --path /webhooks/circleback
Use the printed URL as the endpoint in the automation's Send webhook request step, then send a test request for the most recent meeting.
Reference Materials
- references/overview.md - What Circleback webhooks are, payload fields
- references/setup.md - Create the automation webhook step, get the signing secret
- references/verification.md - Signature verification details and gotchas