We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
The practical way to integrate Replit with Notion is to treat Notion as a normal external API and connect to it from a Repl using the official Notion REST API. You store your Notion API key in Replit Secrets, write a small Node.js or Python script that sends requests to the Notion API, and (optionally) expose a public URL from your Repl if you need to receive Notion webhooks. Nothing in Replit is automatic — you explicitly call Notion’s endpoints, explicitly bind your server to 0.0.0.0, and manage all credentials manually. This works reliably for building dashboards, syncing data, automation tasks, or small web apps that read/write Notion pages or databases.
You integrate Replit with Notion by:
This is the entire formula. You’re basically writing a normal web client or webhook receiver, but running it inside Replit’s environment.
Go to Notion’s official developer page and create an internal integration. This gives you a secret key that starts with secret\_. Treat it as a password — never hardcode it into your code.
Then go to any Notion page or database you want your integration to access and “Share” it with the integration. Notion permissions are explicit; if you don’t share a page/db with your integration, the API will not let you read or write it.
Inside Replit:
The value will appear in the environment as process.env.NOTION_API_KEY (Node.js) or os.environ["NOTION_API_KEY"] (Python).
Below is a simple Node.js example using the official Notion SDK. It reads items from a Notion database. You must replace YOUR_DATABASE_ID with a real ID from Notion.
// index.js
// Run via: node index.js
import { Client } from "@notionhq/client";
const notion = new Client({
auth: process.env.NOTION_API_KEY
});
// Replace with your database ID
const databaseId = "YOUR_DATABASE_ID";
async function readDatabase() {
const response = await notion.databases.query({
database_id: databaseId
});
console.log(response); // Prints database rows
}
readDatabase();
Make sure you install the SDK in Replit:
npm install @notionhq/client
If you want an API endpoint that you can hit from anywhere (e.g., Zapier → Replit → Notion), create a small Express server. Replit requires that servers bind to 0.0.0.0 and listen on process.env.PORT.
// server.js
import express from "express";
import { Client } from "@notionhq/client";
const app = express();
app.use(express.json());
const notion = new Client({
auth: process.env.NOTION_API_KEY
});
const pageId = "PAGE_ID_TO_APPEND_TO"; // Replace with real page ID
app.post("/add-text", async (req, res) => {
const text = req.body.text;
// Appends a paragraph block to a Notion page
await notion.blocks.children.append({
block_id: pageId,
children: [
{
paragraph: {
rich_text: [
{
text: { content: text }
}
]
}
}
]
});
res.json({ status: "ok" });
});
// Replit-required binding
app.listen(process.env.PORT, "0.0.0.0", () => {
console.log("Server running");
});
If you need Notion → Replit direction, Notion can send webhooks. To receive them, you must run the Repl so it exposes a public URL. Replit gives your running server a URL when the Express/Nest/FastAPI server is running. Use that URL as your webhook target inside Notion’s dashboard.
This works, but keep in mind that Repls restart when idle unless you use Deployments. For production webhooks, run as a Deployment or use an external always-on service.
Everything is explicit, but also controllable and reliable if you build around the API correctly.
1
You can pull data from a Notion database into a Replit backend so your app can use Notion as its content store. This works well when you want a simple CMS but don’t want to build an admin UI. Your Repl runs a small server (Node, Python, etc.), fetches data from the Notion REST API using an API key stored in Replit Secrets, and serves that content to a frontend or another service. Since Replit processes may restart, you fetch from Notion on demand or cache data in a file or external DB when needed.
import os
import requests
from flask import Flask
app = Flask(__name__)
NOTION_TOKEN = os.environ["NOTION_TOKEN"]
DATABASE_ID = os.environ["DATABASE_ID"]
@app.route("/content")
def content():
url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28"
}
data = requests.post(url, headers=headers).json()
return data
app.run(host="0.0.0.0", port=8000)
2
A Replit Workflow can run scheduled jobs that push updates into Notion. This is useful for generating daily summaries, backing up logs, or writing computed metrics into a Notion database. Workflows run independently of your web server, so even if your app isn’t running, the automation still executes. You call Notion’s REST API using a secret token, construct a JSON payload, and write new pages or update properties. This avoids manual input and keeps Notion always up‑to‑date.
// workflow_task.py
import os, requests
NOTION_TOKEN = os.environ["NOTION_TOKEN"]
DATABASE_ID = os.environ["DATABASE_ID"]
url = "https://api.notion.com/v1/pages"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
payload = {
"parent": {"database_id": DATABASE_ID},
"properties": {
"Name": {"title":[{"text":{"content":"Daily Summary"}}]}
}
}
requests.post(url, headers=headers, json=payload)
3
You can run a small HTTP server in Replit that receives Notion webhooks whenever a page or database entry changes. This lets your app react instantly—sync to another system, trigger notifications, or kick off automated tasks. You expose a port on Replit, register that public URL in Notion’s developer dashboard, and verify incoming requests using Notion’s signature headers. This setup is fully real-time and works as long as the Repl or Deployment is running.
import os, hmac, hashlib
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["NOTION_WEBHOOK_SECRET"]
@app.post("/notion-webhook")
def notion_webhook():
sig = request.headers.get("X-Notion-Signature")
body = request.data
expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
if sig != expected:
return "invalid signature", 401
event = request.json
// Process event here
return "ok"
app.run(host="0.0.0.0", port=8000)
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
Environment variables fail on Replit when they are read before they exist in the runtime, or when they are stored in the wrong place. For the Notion API, make sure your secret is in Replit Secrets (not in .env), your Repl is restarted after adding it, and you access it through process.env.<YOUR_KEY>.
The Notion token must be placed inside the Replit Secrets tab. Replit does not auto‑load a .env file, so any key stored there is ignored at runtime. After adding a secret, restart the Repl, because environment variables are injected only on startup.
import { Client } from "@notionhq/client"
console.log(process.env.NOTION_TOKEN) // Check if loaded
const notion = new Client({
auth: process.env.NOTION_TOKEN
})
2
A Notion API request in Replit returns 401 when your request is missing or sending the wrong Authorization header. In Replit this almost always comes from an incorrect secret name, a missing env var, or using a Notion key that isn’t connected to the target database or page.
The Notion API only accepts calls that include a valid Bearer token. If your Repl reads an env var that isn’t set, it sends an empty token. Notion then responds 401. Also, a token must be created as an internal integration and the page/database must be explicitly shared with that integration.
import fetch from "node-fetch"
const token = process.env.NOTION_TOKEN // must match Replit Secret name!
const r = await fetch("https://api.notion.com/v1/databases", {
headers: {
"Authorization": `Bearer ${token}`,
"Notion-Version": "2022-06-28"
}
})
3
Calling the Notion API directly from a Replit frontend triggers browser‑side CORS blocks because Notion does not allow client‑side origins. The fix is to call Notion only from your Replit backend server (Node, Python, etc.) and let the browser talk to your server, not to api.notion.com.
Move every Notion request into a backend route, hide the Notion token in Replit Secrets, and fetch only from your Repl’s own domain. This avoids CORS entirely because the browser never contacts Notion directly.
// server.js
import express from "express"
import fetch from "node-fetch"
const app = express()
app.get("/notion", async (req, res) => {
const r = await fetch("https://api.notion.com/v1/databases", {
headers: {
"Authorization": `Bearer ${process.env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28"
}
})
res.json(await r.json())
})
app.listen(3000, "0.0.0.0") // required on Replit
Developers often paste their Notion API key directly into code instead of putting it in Replit Secrets. Replit restarts processes, and anything hard‑coded becomes a security leak when the Repl is public or shared. Always store NOTION\_TOKEN in Secrets and load it from process.env so the token never ends up in the repo or logs.
// Accessing Notion token safely
const notionToken = process.env.NOTION_TOKEN;
Many assume Notion has webhooks or push notifications. It does not. The API is pull-only: your Repl must periodically fetch data using Workflows or a running server. If you design as if Notion will call your server, nothing will update. You must schedule polling yourself using a Workflow task or your own loop.
// Example Notion pagination response structure you must poll through
{ "results": [], "has_more": true, "next_cursor": "..." }
When exposing a local API that communicates with Notion or a frontend, beginners bind the server to localhost instead of 0.0.0.0. Replit will not expose the server if it’s bound only to localhost. This leads to “server not responding” errors even though it works locally.
// Correct express server config for Replit
app.listen(3000, '0.0.0.0');
Notion enforces strict rate limits, and Replit Workflows can hit them fast if you poll aggressively. Developers often retry instantly, causing longer lockouts. Always implement backoff and avoid unnecessary full-database reads. Replit’s restart behavior can amplify bursts if your code retries on boot.
// Basic retry delay on 429 responses
if (res.status === 429) await new Promise(r => setTimeout(r, 1000));
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â