← All SaaS workflows
SaaSCirclebackWebhookWorkflow Automation

Circleback action items to tasks: testing webhook signatures, owners, and replay keys

20 min read

Go deeper on this topic

SaaS integrations

Give AI the context to continue the work: source conversations, open tasks, and unresolved details, from adoption decisions to integration code.

Article 2 of 3 in this series.

View topic page

Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →

Disclosure: this article includes a Circleback affiliate link. We may earn a commission if you subscribe. The article states what we tested and what remains unverified.

Moving meeting action items into a task tracker introduces two decisions immediately: what to do with an unassigned task, and how to handle the same payload arriving again. I built a local adapter from Circleback’s documented webhook schema and tested signatures, missing owners, completed items, and invalid inputs. The adapter keeps uncertain owners and deadlines unresolved and produces a stable key from the meeting and action item IDs.

The experiment was run on September 30, 2026 with synthetic data. It uses no real meeting recordings or customer information. I have not tested delivery from Circleback, writes to an external task tracker, or transcription accuracy. The results below describe the code that processes a payload after receipt.

On October 2, I also tested SQLite storage. Two processes repeatedly saving the same items left three tasks. The added experiment below includes the old-replay case that can reopen a completed task.

For the wider workflow—Slack delivery, huddle capture, and a nightly notes sync—see six Circleback settings for Slack and AI. This article focuses on the next step: exporting action items into a task tracker.

Which work belongs in the task tracker

The target workflow is moving agreed follow-ups into the system where a team tracks ownership and completion. If meeting notes already serve that purpose, another integration may add maintenance without helping anyone finish work. Exporting makes sense when the destination is where people actually update tasks.

I chose a small output contract: title, description, owner email, completion status, and a link back to the meeting. The adapter does not guess an account from a name or infer a deadline from prose. Those guesses could assign work to the wrong person or record a deadline nobody agreed to.

Circleback automations combine conditions and actions. Its documentation describes filters such as tags and participants; leaving conditions empty applies the automation to every meeting. For an initial connection, use a dedicated test scope. Official automation guide

Mapping the documented fields

The adapter uses actionItems fields id, title, description, assignee, and status. An assignee can be null, and status is PENDING or DONE. The action item schema I consulted has no deadline field. Official webhook schema

The right column describes decisions made for this experiment, rather than product features.

Incoming data Adapter output
Meeting ID and action item ID A stable key containing both
Title and description Original text, with outer whitespace removed from the title
Assignee email Email when present; otherwise null and an owner-review flag
Status Preserve PENDING or DONE
No deadline dueDate: null
Meeting ID A source meeting URL

An item with a name but no email still needs an ownership review. Completed items remain in the output, allowing the destination to update an existing task instead of silently retaining its old pending state.

Giving AI the context to continue work also means passing on what remains unresolved. Here, null records an outstanding confirmation rather than hiding a missing value. Keep the source meeting link so the next person can check the basis for an assignment.

Verify the original body before parsing

A signed JSON body and a reserialized object need not contain the same bytes. Spaces and line breaks can change even when the parsed object is identical. Verification therefore runs before JSON parsing, using Circleback’s documented x-signature and HMAC-SHA256 scheme. Signature verification documentation

I used Web Crypto for the local implementation. It rejects malformed signatures, empty secrets, and incorrect secrets. No production secret appears in the sample.

import { verifyCirclebackSignature, buildActionRows } from './circleback-webhook.mjs'

// request comes from the webhook receiver implemented by the operator.
const rawBody = await request.text()
const valid = await verifyCirclebackSignature(
  rawBody,
  request.headers.get('x-signature'),
  signingSecret,
)
if (!valid) throw new Error('Reject request before parsing')
const rows = buildActionRows(JSON.parse(rawBody))

This is an integration sketch, not an HTTP server. In the local test, the original body with whitespace passed verification. Parsing and reserializing that body failed verification against the same signature. Check the receiving framework’s body-parser configuration when connecting a real endpoint.

Replays and task updates

A meeting can contain several tasks, so the meeting ID alone cannot identify each row. I used JSON.stringify([meetingId, actionId]). Unlike joining IDs with a separator, the array representation distinguishes combinations even when an ID contains that separator.

Transforming the same input twice produced identical keys. Changing a title and status while keeping its IDs also retained the key. A destination-side upsert can then update the same task.

Stable keys do not prevent duplicate writes on their own. Two concurrent deliveries could both create a task unless the destination enforces uniqueness or stores the external task ID. Use an atomic upsert backed by a unique constraint, or an equivalent destination-specific mechanism. The September 30 test covered key stability. On October 2, I extended it to the SQLite storage example below.

Two SQLite writers leave only three tasks

I tested replayed action items in a local database using Bun 1.3.11's built-in SQLite driver. This storage example belongs after signature verification. It runs neither a webhook receiver nor an external service.

The table defines the meeting/action key as task_key TEXT NOT NULL PRIMARY KEY. An incoming key uses SQLite's upsert, ON CONFLICT(task_key) DO UPDATE, to update that row's title, owner, status, and other fields. There is no separate “check whether the row exists, then insert” step.

The three items from a meeting are written in one transaction through db.transaction(...).immediate(rows). A SQL failure rolls back that meeting's batch. A test that failed on the second item left zero rows, including no partial first item. Values use bound parameters; a title containing quotes and SQL-like text was retained as text.

Save these four files in the same directory and run Bun. The storage demo is Bun-only. The earlier signature and transformation demo still supports Node.js.

bun task-store-demo.mjs

The demo creates a SQLite file in a temporary directory. Both writer processes finish setup before they receive a start signal. Each saves the same three items 20 times: 40 batches and 120 row upserts. The demo removes its own temporary directory when it ends.

{
  "writers": 2,
  "batches": 40,
  "rowUpserts": 120,
  "storedTasks": 3,
  "pending": 2,
  "pendingOwnerReview": 1,
  "afterUpdateStatus": "DONE",
  "afterOldReplayStatus": "PENDING"
}

Only three tasks remained after the concurrent writer phase: two pending, with one needing an ownership review. A missing owner email and deadline remained null. A separate test with the same action IDs in a different meeting left six rows, keeping tasks from different meetings distinct.

An old replay can reopen DONE even without duplicate rows

The last two fields show the limit. Updating item 101 to DONE produced afterUpdateStatus: "DONE". Replaying its original PENDING payload then changed that same row back to PENDING. The primary key and upsert prevent duplicate rows; they cannot determine which input is newer.

This sample accepts the last input written. Before connecting it to a real workflow, decide whether meeting data may overwrite a status someone updated in the destination. Fetching the source's current state or comparing a trustworthy update version would require further implementation. The selected adapter fields do not establish event order, and this example does not implement that policy.

Seven tests cover replays, updates, separate meetings, batch rollback, malformed input, the old-replay result, and two-writer storage. These counts describe synthetic data in local SQLite. They do not measure real Circleback delivery, an external task tracker, or storage performance.

Results from three synthetic action items

The fixture contains one assigned pending item, one unassigned pending item, and one completed item. Its email address, owner@example.com, is a test value. Running the companion demo produces these counts alongside two example referral URLs:

{
  "rows": 3,
  "pending": 2,
  "pendingOwnerReview": 1,
  "stableKeys": true
}

One of the two pending items requires an ownership review. The completed item remains in the output but is excluded from the pending count. These results show that the adapter preserves the supplied state. They say nothing about how accurately Circleback extracts work or owners from an actual conversation.

I also tested a missing action array, a string ID, an empty title, an unknown status, a malformed assignee, and duplicate action IDs within one payload. Each invalid input stops transformation. An empty action array returns zero rows normally; a meeting with no action items should not create tasks.

Reproduce the experiment

Save these three files into the same directory and run the demo with Bun or Node.js 22+. No dependencies or product account connections are required.

bun demo.mjs
# With Node.js
node demo.mjs

The demo starts no listener and writes to no external service. Change an owner email to null or change an item’s status to inspect how those inputs affect the local output.

Deciding whether to connect real meetings

For a real connection, scope the automation to approved meetings and select only the outcomes the destination needs. This adapter does not use recordings or full transcripts. Review the destination, exported fields, and participant communication before enabling delivery. The documented UI steps are in the webhook setup guide.

A synthetic-data test cannot establish deployment readiness. Use a small set of approved meetings to check missed follow-ups, owner matching, completion updates, and delivery failures. Start with human review before notifications are sent, so an incorrect assignment is not immediately circulated.

This integration is worth evaluating when a team already maintains follow-ups in a task tracker and manual copying is a recurring burden. For a few meetings that are already manageable by hand, maintaining a webhook receiver and storage may be harder to justify. Decide that before building the connection.

The companion article on Dub referral tracking with bilingual UTMs separates language-specific link activity from commission attribution.

FAQ

Q. Did you benchmark real meeting transcription?
No. This is a local integration experiment using synthetic payloads and official documentation. Japanese transcription accuracy, real meeting summaries, and paid-plan benefits were not measured.
Q. Does the sample prevent duplicate tasks?
The SQLite example uses the meeting/action key as a primary key with an upsert. Two writers saving the same three items in 40 batches left three tasks. An old PENDING replay can still reopen DONE, so event ordering needs a separate policy. External task trackers have not been tested.