Build a TradingView-to-Broker Webhook Bridge
Architect, build, deploy, and monitor a small service that receives TradingView alerts and forwards validated messages to a broker integration layer.
This guide walks the full lifecycle of a small but realistic automation service: a bridge that receives TradingView alert webhooks, validates them, and hands them to a broker-integration layer. We cover architecture, code, deployment, and monitoring.
This is an engineering guide. The bridge moves and validates messages; it does not decide what to trade and is not advice. Connecting to live execution is your responsibility and carries real financial risk.
Architecture
The design separates three concerns so each can fail independently:
- Ingress — a tiny HTTP endpoint that authenticates and validates alerts.
- Queue — a Redis list/stream that decouples ingress from processing.
- Worker — a process that pops messages and calls the broker layer.
Decoupling matters: TradingView retries on non-200 responses, so the ingress must respond fast and never block on slow downstream work.
The ingress endpoint
The endpoint authenticates with a shared secret, validates the payload, enforces idempotency, and enqueues — then returns immediately.
import express from "express";
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const app = express();
app.use(express.json({ limit: "16kb" }));
app.post("/webhook", async (req, res) => {
if (req.body?.secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
const { id, symbol, action } = req.body;
if (!id || !symbol || !["buy", "sell"].includes(action)) {
return res.status(422).json({ error: "invalid payload" });
}
// Idempotency: SET NX returns null if the key already existed.
const fresh = await redis.set(`seen:${id}`, "1", { NX: true, EX: 86400 });
if (!fresh) return res.json({ ok: true, duplicate: true });
await redis.lPush("alerts", JSON.stringify({ id, symbol, action }));
return res.json({ ok: true });
});
app.listen(8080);The worker
The worker blocks on the queue and processes one message at a time, with retries handled explicitly rather than by re-queuing blindly.
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
while (true) {
const popped = await redis.blPop("alerts", 0); // block until a message
const msg = JSON.parse(popped.element);
try {
await handle(msg); // your broker-integration call goes here
} catch (err) {
console.error("processing failed", msg.id, err);
await redis.lPush("alerts:dead", JSON.stringify({ msg, error: String(err) }));
}
}
async function handle(msg) {
// Validate again, check risk limits, then call the broker layer.
console.log("handling", msg.symbol, msg.action);
}Deployment
Package both processes with Docker and run them with the queue:
services:
ingress:
build: { context: ., dockerfile: Dockerfile.ingress }
environment: [REDIS_URL=redis://redis:6379, WEBHOOK_SECRET]
ports: ["8080:8080"]
depends_on: [redis]
worker:
build: { context: ., dockerfile: Dockerfile.worker }
environment: [REDIS_URL=redis://redis:6379]
depends_on: [redis]
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]Put the ingress behind HTTPS (a reverse proxy such as Caddy or Nginx). Never expose the webhook secret in client code.
Monitoring
At minimum, track:
- Queue depth (
LLEN alerts) — a rising backlog means the worker can't keep up. - Dead-letter count (
LLEN alerts:dead) — any growth needs investigation. - Ingress latency and 4xx/5xx rates — TradingView retries amplify errors.
Export these as metrics (e.g. to Prometheus) and alert on backlog and dead-letter growth.
Scaling
For higher throughput, switch the Redis list to a Redis Stream with consumer groups so multiple workers share the load with acknowledgements and replay. Add per-symbol rate limits and a circuit breaker around the broker call so a single failing dependency can't cascade.
You now have a decoupled, observable bridge — a solid foundation you can extend with risk checks and a real execution layer at your own discretion and risk.