
Integrating Transcription With Notion: API Recipe (2026)
Summarize this article with:
The Working Paths
There is no native Notion integration for ConvertAudioToText in the Notion marketplace today. What exists is a clean API path: register a webhook endpoint with the CATT API, receive a job.completed event when a transcript finishes, fetch the transcript from the returned URL, and write it to a Notion database. About 120 lines of Node.js, an afternoon of setup, and every recording your team makes becomes searchable Notion content.
Three patterns cover most team setups. This guide walks through the database-row pattern in full, with notes on the others.
Database row per transcript. A "Transcripts" database with columns for date, speaker, project, summary, and a link to the result. Best for teams that filter and sort across many recordings.
Appended to existing project pages. Each transcript appends blocks to an existing project or meeting page. Best for teams where transcripts belong inside broader project notes.
One page per speaker or interviewee. Maintain a speaker-to-page-id mapping and route transcripts accordingly. Useful for journalists with recurring sources and sales teams tracking key accounts.
Step 1: Create the Notion Database
In Notion, create a new full-page database with these properties:
| Property | Type | Use |
|---|---|---|
| Title | Title | Filename or meeting topic |
| Date | Date | Recording date |
| Speakers | Multi-select | Who is in the recording |
| Project | Select | Which project this belongs to |
| Summary | Text | Summary if generated |
| Duration | Number | Audio length in seconds |
| Language | Select | Detected or specified language |
| Result URL | URL | Link back to the CATT result page |
Copy the database ID from the URL. It is the 32-character hex string that follows the last slash and precedes any ?v= query parameter.
Step 2: Create a Notion Integration
- Go to notion.so/my-integrations and click "New integration."
- Name it (for example, "Transcription Integration"), select your workspace, choose Internal type, and submit.
- Copy the Internal Integration Token. Since September 2024, new tokens start with
ntn_rather than the oldersecret_prefix. Both formats work identically in API calls. - Open your Transcripts database in Notion, click the "..." menu, go to "Add connections," and select your integration.
The integration only has access to databases you explicitly share with it.
Step 3: Register a Webhook with CATT
Webhooks on ConvertAudioToText require the Business plan. Register your endpoint:
curl -X POST https://api.convertaudiototext.com/api/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/catt",
"events": ["job.completed", "job.failed"]
}'
The response includes a secret field. Store it as CATT_WEBHOOK_SECRET in your environment. You cannot retrieve this value again after creation.
For the polling alternative (no webhook required), see webhook vs polling for transcripts.
Step 4: The Webhook Handler
The job.completed payload includes a result_url field pointing to the full transcript. The handler verifies the signature, checks the event type, fetches the transcript, and then writes to Notion.
import express from 'express';
import { Client as NotionClient } from '@notionhq/client';
import crypto from 'crypto';
const app = express();
const notion = new NotionClient({ auth: process.env.NOTION_TOKEN });
const NOTION_DB_ID = process.env.NOTION_DATABASE_ID;
app.post(
'/webhooks/catt',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sigHeader = req.header('X-Webhook-Signature'); // "sha256=<hex>"
const [, sig] = (sigHeader || '').split('=');
const expected = crypto
.createHmac('sha256', process.env.CATT_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('Bad signature');
}
const event = JSON.parse(req.body.toString());
if (event.event !== 'job.completed') return res.sendStatus(200);
// Fetch the full transcript from the result URL
const result = await fetch(event.data.result_url, {
headers: { Authorization: `Bearer ${process.env.CATT_API_KEY}` }
}).then(r => r.json());
await createNotionPage({
jobId: event.data.job_id,
language: event.data.language,
durationSeconds: event.data.duration,
completedAt: event.data.completed_at,
resultUrl: event.data.result_url,
transcript: result.transcript,
summary: result.summary,
utterances: result.utterances,
filename: result.filename,
});
res.sendStatus(200);
}
);
The event fields that are always present on job.completed: event, timestamp, data.job_id, data.status, data.created_at, data.language, data.duration, data.completed_at, data.result_url. The full transcript text, summary, and utterances come from the result fetch, not from the event itself.

Step 5: Write the Notion Page
async function createNotionPage(data) {
const transcriptBlocks = buildTranscriptBlocks(data.utterances || []);
const page = await notion.pages.create({
parent: { database_id: NOTION_DB_ID },
properties: {
Title: {
title: [{ text: { content: data.filename || `Transcript ${data.completedAt?.slice(0, 10)}` } }]
},
Date: { date: { start: data.completedAt?.slice(0, 10) || new Date().toISOString().slice(0, 10) } },
Language: { select: { name: data.language || 'auto' } },
Duration: { number: data.durationSeconds },
'Result URL': { url: data.resultUrl },
Summary: {
rich_text: [{ text: { content: (data.summary || '').slice(0, 2000) } }]
}
}
});
// Append transcript blocks in batches of 100 (Notion API limit)
if (transcriptBlocks.length > 0) {
await appendBlocksInBatches(page.id, transcriptBlocks);
}
}
function buildTranscriptBlocks(utterances) {
const blocks = [];
blocks.push({
object: 'block',
type: 'heading_2',
heading_2: { rich_text: [{ text: { content: 'Transcript' } }] }
});
for (const u of utterances) {
// Notion rich_text.content is capped at 2,000 characters per block
const label = u.speaker ? `[${u.speaker}] ` : '';
const text = (label + (u.text || '')).slice(0, 2000);
blocks.push({
object: 'block',
type: 'paragraph',
paragraph: { rich_text: [{ text: { content: text } }] }
});
}
return blocks;
}
async function appendBlocksInBatches(pageId, blocks) {
for (let i = 0; i < blocks.length; i += 100) {
const batch = blocks.slice(i, i + 100);
await notion.blocks.children.append({ block_id: pageId, children: batch });
if (i + 100 < blocks.length) {
await new Promise(r => setTimeout(r, 400)); // stay inside 3 req/s average
}
}
}
The 100-block-per-request limit is a hard Notion API constraint. The 400ms pause keeps you inside the 3-requests-per-second average. For very long recordings (100+ utterances), this means the write takes a few seconds; that is acceptable for an async post-processing step.
Variation: Append to an Existing Project Page
If transcripts belong inside project pages rather than a standalone database, use blocks.children.append against an existing page ID:
async function appendToProjectPage(projectPageId, data) {
const divider = { object: 'block', type: 'divider', divider: {} };
const heading = {
object: 'block',
type: 'heading_3',
heading_3: {
rich_text: [{ text: { content: `Meeting transcript ${data.completedAt?.slice(0, 10)}` } }]
}
};
const transcriptBlocks = buildTranscriptBlocks(data.utterances || []);
await appendBlocksInBatches(projectPageId, [divider, heading, ...transcriptBlocks]);
}
No-Code Paths
If you want this without writing a server, two options work today.
Zapier. The flow: Webhook trigger (catches the CATT job.completed event) then a Code step to fetch the result URL, then a "Create Database Item" action in Notion. Multi-step zaps require a paid Zapier plan. Zapier is faster to set up than custom code but harder to debug when Notion writes fail silently.
n8n. There is a published n8n workflow template that chains Whisper transcription to a Notion page. The self-hosted version of n8n is free with unlimited executions and lets you keep recordings out of vendor-owned clouds. Make.com also has a Notion module that works the same way. See automating transcription with Zapier for a step-by-step no-code walkthrough.
Fireflies.ai has a native Notion integration (no Zapier needed) that pushes meeting summaries, action items, and a transcript link into a Notion database after each recorded call. It is the right choice if you primarily need meeting bots rather than file uploads.
Notion API Version Note (2026)
The Notion API version 2026-03-11 introduced three breaking changes worth knowing if you are upgrading existing integrations:
- The
afterparameter in append-block-children is replaced byposition. - The
archivedfield is replaced byin_trash. - The
transcriptionblock type is renamedmeeting_notes.
If you are writing new code today, install @notionhq/client at v5.12.0 or later and set notionVersion: "2026-03-11" when constructing the client if you want the latest behavior. Older token formats (secret_ prefix) continue to work.
Production Patterns
Journalism teams. Every recorded interview lands in a searchable Notion database. Property filters by topic, source, and date let reporters pull all quotes from a source in seconds. For the broader interview workflow, see how to transcribe an interview recording.
Customer success teams. Every customer call lands in a per-customer Notion page. Quarterly business review prep drops from hours of re-listening to 15 minutes of reading.
Podcast production. Each episode gets a Notion page with transcript, summary, and the result URL for editing. The producer hands off to the social team without sharing the raw audio file. See best transcription for podcasts for accuracy comparisons across common tools.
If you just need a clean transcript without a webhook server, ConvertAudioToText handles the upload and produces speaker-labeled output you can copy-paste or export to paste directly into any Notion page.
For building the meeting-minutes layer on top of the raw transcript, create meeting minutes from audio covers the structured output patterns that map cleanly to Notion database properties.
FAQ
Does ConvertAudioToText have a native Notion integration?
Not yet. There is no one-click connection in the Notion integrations marketplace. The working path is to register a webhook endpoint (Business plan) that receives a job.completed event, then fetch the transcript via the result_url and write it to Notion using the Notion API.
What Notion API limits should I plan for?
The Notion API allows an average of 3 requests per second per integration. Each append-block-children call accepts at most 100 blocks. Rich text content per block is capped at 2,000 characters. For long transcripts, batch your block appends in chunks of 100 with a short delay between calls.
Do I need a paid Notion plan to use the API?
No. The Notion API is free on all Notion plans, including the free tier. You only need a paid plan for Notion features like sharing with guests beyond certain limits. API rate limits are the same across all plans.
What is the no-code path for sending transcripts to Notion?
Two good options: Zapier (paid tier needed for multi-step zaps) lets you catch a webhook and create a Notion page with no code. n8n Cloud has a Whisper-to-Notion template, and the self-hosted version runs for free. Both paths suit simple transcript-to-page flows; custom code is better for speaker-labeled blocks, batching, and long files.
Sources
- Notion API: Request limits - verified 2026-07-02
- Notion API: Append block children - verified 2026-07-02
- Notion API upgrade guide 2026-03-11 - verified 2026-07-02
- Fireflies Notion integration guide - verified 2026-07-02
- Notion integrations marketplace - verified 2026-07-02 (no native transcription connectors listed)
- @notionhq/client on npm - v5.12.0 supports API version 2026-03-11
Try transcription free
Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 30 minutes free, no account.
Related Articles

Transcription into HubSpot: Verified Integration Paths
How to get call transcripts into HubSpot: what Conversation Intelligence covers natively, and the API recipe for external recordings, dialers, and languages it misses.

Transcription to Slack: The Paths That Actually Work
Three verified ways to get transcripts into Slack in 2026: Otter and Fireflies native apps, a webhook-to-Slack pattern, and CATT's built-in Slack notification setting.