Learn how to build an integration hub using Replit. Follow step-by-step instructions to connect apps, automate workflows, and boost productivity.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build an Integration Hub on Replit, you’ll create a Node.js backend (using Express) that connects to external APIs, stores user configurations (via a small database or file), and provides routes for sending/receiving data. This backend acts as the “hub” — handling API calls on behalf of your integrations. In Replit, you can easily manage environment variables through the “Secrets” tab, test endpoints with the built-in webview, and even collaborate live. Below is a practical, real-world setup that actually runs inside Replit and can scale from a basic proof-of-concept to a real small integration hub.
Start from the Replit template called “Node.js”. It automatically gives you an index.js entry file and adds Node dependencies management. This file is what launches when you click “Run.”
// In the Replit shell, if not already added, install Express
npm install express axios
Open index.js (it’s in the root folder). Replace its contents with this code. This will be our integration hub’s core — handling basic routing, connection testing, and forwarding data.
// index.js
import express from "express"
import axios from "axios"
const app = express()
app.use(express.json())
// Basic check endpoint
app.get("/", (req, res) => {
res.send("Integration Hub is running! 🚀")
})
// Example integration endpoint
app.post("/send-to-slack", async (req, res) => {
try {
// Access your Slack webhook URL from Replit Secrets
const slackWebhook = process.env.SLACK_WEBHOOK_URL
// message payload received from client
const { message } = req.body
// Send to Slack
await axios.post(slackWebhook, { text: message })
res.status(200).json({ success: true, sent: message })
} catch (error) {
console.error(error)
res.status(500).json({ success: false, message: "Failed to send message" })
}
})
// Replit provides PORT automatically, default to 3000 if not set
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Server running on port ${port}`))
What this does: It creates a small Express server that listens for requests. The /send-to-slack endpoint takes a message and pushes it to Slack using a webhook URL stored securely in Replit Secrets.
In the left sidebar, click the 🔒 “Secrets” icon. Add new secrets like:
This ensures sensitive data doesn’t live inside your code. In Replit, the environment variable will be available as process.env.SLACK_WEBHOOK_URL.
Create a folder named integrations in the root of your project. Inside, you can create small service files for each external system (for example Slack, Notion, or Gmail). These files should only handle one responsibility — communicating with that external API.
Create a new file: integrations/slack.js
// integrations/slack.js
import axios from "axios"
export async function sendSlackMessage(text) {
const webhookUrl = process.env.SLACK_WEBHOOK_URL
const response = await axios.post(webhookUrl, { text })
return response.data
}
Then update index.js to use it:
// index.js (replace the /send-to-slack endpoint)
import express from "express"
import { sendSlackMessage } from "./integrations/slack.js"
const app = express()
app.use(express.json())
app.post("/send-to-slack", async (req, res) => {
try {
const { message } = req.body
const result = await sendSlackMessage(message)
res.json({ success: true, result })
} catch (err) {
res.status(500).json({ success: false, error: err.message })
}
})
Click “Run” in Replit. The small window on the right should now display “Integration Hub is running!” Copy the public URL above that preview window.
You can test sending a message to Slack by using Replit’s shell:
curl -X POST https://your-repl-username.repl.co/send-to-slack \
-H "Content-Type: application/json" \
-d '{"message":"Hello from Replit Integration Hub!"}'
If everything is wired correctly, your Slack channel will receive the message.
If you need to track integration logs or store config, Replit supports SQLite out-of-the-box.
npm install better-sqlite3
Create db.js file:
// db.js
import Database from "better-sqlite3"
const db = new Database("hub.db")
// Create a simple table (runs only once)
db.prepare("CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, event TEXT, date TEXT)").run()
export function saveLog(event) {
db.prepare("INSERT INTO logs (event, date) VALUES (?, ?)").run(event, new Date().toISOString())
}
Then call saveLog() inside your integration routes to record each action.
Replit allows live multiplayer editing by default. You can invite teammates using the “Invite” button. Each team member will see real-time updates and can even use the “Chat” pane for collaboration. For deployment, your Repl runs automatically on a public URL as long as it’s active; for persistent uptime, use Replit’s “Always On” if available in your plan.
console.log output immediately).By following this setup exactly as shown, you’ll have a real, working Integration Hub running on Replit — capable of connecting multiple external APIs, managing credentials securely, and handling real requests like a production-grade small service.
import express from "express"
import bodyParser from "body-parser"
import axios from "axios"
const app = express()
app.use(bodyParser.json())
// Example integration registry
// In a real case these baseUrls or tokens would be in Replit Secrets
const integrations = {
github: { baseUrl: "https://api.github.com", token: process.env.GH\_TOKEN },
slack: { baseUrl: "https://slack.com/api", token: process.env.SLACK\_TOKEN },
}
// Simple routing layer for your integration hub
app.post("/integrate/:service/:action", async (req, res) => {
const { service, action } = req.params
const { payload } = req.body
const integration = integrations[service]
if (!integration) return res.status(400).json({ error: "Unknown service" })
try {
const headers = { Authorization: `Bearer ${integration.token}` }
const endpoint = getEndpoint(service, action)
const response = await axios.post(`${integration.baseUrl}${endpoint}`, payload, { headers })
res.json({ data: response.data })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Example endpoint builder
function getEndpoint(service, action) {
const map = {
github: {
createIssue: "/repos/:owner/:repo/issues",
},
slack: {
sendMessage: "/chat.postMessage",
},
}
return map[service]?.[action] || ""
}
app.listen(3000, () => console.log("Integration hub running on port 3000"))
import express from "express"
import crypto from "crypto"
const app = express()
app.use(express.json())
// Endpoint to receive webhook events & proxy them to connected integrations
app.post("/webhook/:source", async (req, res) => {
const { source } = req.params
const secret = process.env[`SECRET_${source.toUpperCase()}`]
const signature = req.headers["x-signature"]
const payload = JSON.stringify(req.body)
if (!verifySignature(payload, signature, secret)) {
return res.status(403).json({ error: "Invalid signature" })
}
// Example: fan out webhook event to multiple integrations
const event = { source, data: req.body }
try {
await notifyIntegrations(event)
res.status(200).json({ received: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac("sha256", secret).update(payload).digest("hex")
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac))
}
async function notifyIntegrations(event) {
// In a real app: store and fetch subscriber URLs from DB
const subscribers = JSON.parse(process.env.INTEGRATION\_TARGETS || "[]")
const fetch = (await import("node-fetch")).default
await Promise.all(
subscribers.map(url =>
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
})
)
)
}
app.listen(3000, () => console.log("Webhook proxy live on port 3000"))
import express from "express"
import axios from "axios"
import crypto from "crypto"
const app = express()
app.use(express.json())
// OAuth redirect handler for multiple external APIs using shared redirect URI in Replit
app.get("/oauth/callback/:provider", async (req, res) => {
const { provider } = req.params
const { code, state } = req.query
const config = getProviderConfig(provider)
if (!config) return res.status(400).json({ error: "Unknown provider" })
try {
// Exchange code for token
const tokenResponse = await axios.post(config.tokenUrl, {
client\_id: process.env[`${provider.toUpperCase()}_CLIENT_ID`],
client\_secret: process.env[`${provider.toUpperCase()}_CLIENT_SECRET`],
redirect\_uri: config.redirectUri,
code,
grant_type: "authorization_code",
})
const accessToken = tokenResponse.data.access\_token
// Store encrypted token in memory or DB (example: temporary memory)
const encrypted = encryptToken(accessToken, process.env.SECRET\_KEY)
tokenStore[provider] = encrypted
res.redirect(`/connected?provider=${provider}`)
} catch (err) {
res.status(500).json({ error: "Failed to complete auth", detail: err.message })
}
})
// Example secured request proxy
app.post("/api/:provider/:endpoint", async (req, res) => {
const { provider, endpoint } = req.params
const tokenEncrypted = tokenStore[provider]
if (!tokenEncrypted) return res.status(403).json({ error: "Not connected" })
const token = decryptToken(tokenEncrypted, process.env.SECRET\_KEY)
try {
const response = await axios.post(
`${getProviderConfig(provider).apiBase}/${endpoint}`,
req.body,
{ headers: { Authorization: `Bearer ${token}` } }
)
res.json(response.data)
} catch (err) {
res.status(500).json({ error: "API call failed", detail: err.message })
}
})
function getProviderConfig(provider) {
const base = process.env.REPLIT\_URL || "http://localhost:3000"
return {
github: {
tokenUrl: "https://github.com/login/oauth/access\_token",
apiBase: "https://api.github.com",
redirectUri: `${base}/oauth/callback/github`,
},
notion: {
tokenUrl: "https://api.notion.com/v1/oauth/token",
apiBase: "https://api.notion.com/v1",
redirectUri: `${base}/oauth/callback/notion`,
},
}[provider]
}
const tokenStore = {}
function encryptToken(token, key) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv("aes-256-gcm", Buffer.from(key, "hex"), iv)
const encrypted = Buffer.concat([cipher.update(token), cipher.final()])
const tag = cipher.getAuthTag()
return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`
}
function decryptToken(data, key) {
const [ivHex, tagHex, encHex] = data.split(":")
const iv = Buffer.from(ivHex, "hex")
const tag = Buffer.from(tagHex, "hex")
const enc = Buffer.from(encHex, "hex")
const decipher = crypto.createDecipheriv("aes-256-gcm", Buffer.from(key, "hex"), iv)
decipher.setAuthTag(tag)
const decrypted = Buffer.concat([decipher.update(enc), decipher.final()])
return decrypted.toString()
}
app.listen(3000, () => console.log("Integration auth bridge running on port 3000"))

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
When building an Integration Hub on Replit — meaning a central web app that connects to multiple third-party services (like Slack, Google, or custom APIs) — the best practice is to structure your project cleanly, isolate secrets, and handle network calls with async-safe code. You should use a simple Express.js (Node.js) backend combined with a lightweight frontend (HTML or React if needed). Replit can easily host this as a public web server, but you must be careful with API keys, persistent storage, and rate limits. The goal is to create clean separation: frontend for user interface, backend for integrating with external APIs, and environment variables for credentials.
Inside your Replit project (a “Repl”), keep a clear structure like:
routes/slack.js, routes/google.js)
In Replit, create a file named index.js. This file will start your web server and load environment variables safely. Use Replit’s built-in “Secrets” tab (lock icon in sidebar) to define credentials like GOOGLE_API_KEY or SLACK_TOKEN. These will be available inside your Node app as process.env.MY_SECRET\_NAME.
// index.js
import express from "express" // express framework for routing
import cors from "cors"
import dotenv from "dotenv"
dotenv.config() // loads .env fallback in local environments
const app = express()
app.use(express.json()) // parse incoming JSON
app.use(cors()) // needed when you'll call from another domain or Replit frontend
app.get("/", (req, res) => {
res.send("Integration Hub is running!")
})
// Import integration routes
import slackRouter from "./routes/slack.js"
import googleRouter from "./routes/google.js"
app.use("/slack", slackRouter)
app.use("/google", googleRouter)
// Replit exposes process.env.PORT for your web server
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Server running on port ${port}`))
Inside a new folder called routes/, create a file named slack.js. This handles all requests related to the Slack integration. The same pattern can be used for any API (Google, Notion, etc.) — each gets its own route file.
// routes/slack.js
import express from "express"
import fetch from "node-fetch" // built-in in newer Node versions, else install with npm
const router = express.Router()
router.post("/send-message", async (req, res) => {
// this endpoint expects a 'text' field in the request body
const { text } = req.body
if (!process.env.SLACK_TOKEN) {
return res.status(500).send("Missing Slack token in Secrets")
}
const response = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SLACK_TOKEN}`,
},
body: JSON.stringify({
channel: "#general", // your Slack channel
text: text || "Hello from Replit Integration Hub!",
}),
})
const data = await response.json()
res.json(data)
})
export default router
Place similar route files (like google.js) inside the same folder for other APIs. Keep each one focused: fetch external data, parse it, and return clean JSON responses. This modular approach makes debugging easier and prevents messy logic in a single file.
On Replit, click the lock icon (Secrets tab) in the left sidebar and add your keys one by one:
Never hardcode values directly in your code. They’re visible publicly if the Repl is shared. Using Secrets prevents leaks and works identically in the deployed web URL.
If you want a small frontend dashboard, create an index.html file inside public/ and add this snippet in your Express config to serve it:
// Add inside index.js before routes
app.use(express.static("public"))
This makes Replit serve your HTML interface from https://your-repl-name.username.repl.co. That page can perform fetch() calls to your backend routes (like /slack/send-message).
If your Integration Hub needs to save user preferences or logs, use Replit’s built-in Replit Database or connect an external one (like MongoDB Atlas). For small-scale projects, this is enough:
// services/db.js
import Database from "@replit/database"
const db = new Database()
// Example usage
export const saveMessage = async (key, value) => {
await db.set(key, value)
}
export const getMessage = async (key) => {
return await db.get(key)
}
Then import it into your routes to store or retrieve integration data safely:
import { saveMessage } from "../services/db.js"
// After successful Slack message send
await saveMessage("lastMessage", text)
You don’t need to “deploy” manually — once your Repl runs, it hosts your server automatically on a public URL. If you’re testing webhooks (for example, Slack outgoing webhooks or OAuth callbacks), copy the Replit URL shown by the “Open in new tab” button and use it directly as the callback URL in your external service configuration.
A common pitfall is the Replit sleep mode: Always-On Repls (paid feature) are necessary if you need your hub online 24/7; otherwise, the Repl stops when idle. For testing or development, the free plan works fine.
Following these patterns gives you a stable Integration Hub hosted on Replit — modular, secure with Secrets, and easy to expand by adding new route files for more services as your app grows.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.