Skip to main content

Documentation Index

Fetch the complete documentation index at: https://uncoded.ch/docs/llms.txt

Use this file to discover all available pages before exploring further.

The SignalsBot is a tiny, security-focused webhook receiver. It accepts signed HTTPS POSTs from your SignalEditor, from TradingView, or from any other source you authenticate, validates them carefully, and writes the resulting configuration changes into your local database — where the TradingBot picks them up.

What the SignalsBot does for you

The SignalsBot is the single, validated entry point for external trade signals. If you’re publishing strategies from your SignalEditor, this is the receiver. If you’re sending TradingView alerts to your bot, this is the receiver. If you have a custom script or a third-party signal provider, this is also the receiver. Everything that wants to change your TradingBot’s behavior from the outside passes through here — and nothing reaches your local database without being authenticated and validated first. The SignalsBot is intentionally one of the smallest services in the stack. Its job is narrow, its surface is auditable, and its failure mode is contained.

Receives webhooks securely

HMAC-SHA256 signature verification for cryptographic integrity, plus shared-secret token fallback for sources (like TradingView) that don’t support HMAC.

Validates every payload

Type-checks every field, rejects malformed shapes, enforces array length consistency (e.g., sellPercentages.length === buySplits), and refuses unknown keys.

Writes to local database only

Updates two specific tables with a defined set of allowed fields. Never touches your exchange, never holds your API keys.

Audits every accepted change

Every successful webhook is logged with source IP, timestamp, and the exact change applied. Every rejected webhook is logged with the precise reason.

Rate-limited at the door

Global limit of 100 requests per 15-minute window per IP. Misbehaving sources get throttled before they can flood your dashboard.

64 KB body cap

Inbound JSON bodies are capped to prevent memory-flood attacks. Real signal payloads are typically a few hundred bytes — the cap is generous for legitimate use.

Standard security headers

Every response includes the standard set of security headers — content-type sniffing protection, frame protection, XSS protection. No surprises in misconfigured browsers.

Bounded surface area

Two operations, four endpoints (GET /, GET /health, GET /tradepairs, POST /webhook). Every line of behaviour is auditable.

Why this is its own service

You might wonder: why is the SignalsBot a separate container instead of an endpoint inside the TradingBot? Two reasons. Security boundary. The SignalsBot is the only internet-exposed component (besides the Dashboard). By making it a small, audit-clear, one-job service, the security-sensitive surface is minimized. The TradingBot — which holds your exchange API keys — is never internet-exposed. Independent failure mode. If the SignalsBot crashes (or is being attacked), your trading is unaffected. The TradingBot continues running with its existing configuration. You get a webhook outage, not a trading outage.
The SignalsBot is the most security-sensitive surface in the unCoded stack. It accepts external HTTP POSTs that — if validated successfully — change live trading configuration.Because of this, the bot is intentionally minimal. It does one thing: validate webhooks and apply specific, allowed configuration changes. No admin panel. No remote-management endpoint. No “add new connector” feature. Just one validated POST endpoint.Always put the SignalsBot behind a TLS reverse proxy (Caddy, nginx-proxy-manager, or similar). Never expose its raw HTTP port to the public internet.

What you can change via webhook

The SignalsBot accepts two kinds of operations — and only these two.
A tradepair update controls which symbols your TradingBot is actively trading.The webhook can:
  • Add a symbol to the active set (e.g., start trading ETHUSDT)
  • Remove a symbol from the active set (e.g., stop trading XRPUSDT)
  • Replace the entire active set at once
Behind the scenes, the SignalsBot updates a single tradePairs configuration entry that the TradingBot reads on every configuration poll. The change is picked up within seconds.Use case examples:
  • A “trend filter ON” strategy enables a symbol when conditions allow.
  • A “trend filter OFF” strategy disables a symbol when conditions degrade.
  • A daily script rotates the active set based on volatility ranks — high-vol symbols on, low-vol off.
  • An emergency-stop script removes all symbols at once when news hits.
A mode-settings update changes the parameters of one or more pre-built modes — without affecting which symbols are active.Allowed fields include:
  • canBuy, canSell — enable or disable buying / selling for the mode
  • canBuyUp, canBuyDown — directional gating
  • start — the mode’s starting reference price
  • buyPercentage, buySplits, buyVolumes, investmentPerBuy, investmentPercentMode — the buy-ladder shape
  • sellPercentages, sellActivateDistancePercentage, sellCancelDistancePercentage — the sell-ladder shape and trailing-stop activation
  • stopLoss, stopLossPercentage — the hard stop-loss
  • onlyMakerBuy, onlyMakerSell — enforce maker-only orders
  • sellTimeCurves, sellTimeCurveCheckIntervalMs — time-decay sell logic
  • name, description — labelling
Any field not in this list is rejected by validation. There is no path for an arbitrary configuration change.Use case examples:
  • A “regime change” strategy widens the sell ladder when volatility expands.
  • A “news-mode” toggle enables canBuy: false for risky modes when high-impact news hits.
  • A maker-rebate mode adjusts the buy-ladder spacing dynamically.
  • A “weekend conservative” toggle reduces position sizing on Friday evening, restores Monday morning.

How a webhook flows through the SignalsBot

1

HTTPS POST arrives at /webhook

Your reverse proxy terminates TLS and forwards the request to the SignalsBot. Standard security headers are already applied. The global rate limiter checks the source IP — if it’s exceeded 100 requests in the last 15 minutes, the request is rejected with 429 Too Many Requests.
2

Authentication

The SignalsBot looks for one of three credentials:
  • An X-Signature header (HMAC-SHA256 of the raw body)
  • An X-TRADINGVIEW-TOKEN header (shared secret)
  • A secret field inside the JSON body (shared secret)
Any one of these passes authentication. None match → 401 Unauthorized and the bot logs the rejection.
3

Payload classification

The bot inspects the body to decide what operation to perform:
  • If tradePairs, pair, or symbol is present → tradepair update.
  • If mode (or modes) is present along with allowed mode-settings keys → mode-settings update.
  • Both can be present — they’re processed in order.
  • Neither match → 400 Bad Request with a precise reason.
4

Validation

Every field is type-checked. Length consistency is enforced (e.g., the number of sell percentages must match the number of buy splits). Unknown keys are stripped. Out-of-range values are rejected.
5

Database write

The validated change is written to the local database — either to the config.tradePairs key, the modes table, or both. The configuration hash is updated, which is how the TradingBot detects “something changed, re-read.”
6

Audit log

Every successful change is logged in the format [AUDIT] <source IP> <action> <details>. The audit log is preserved in the bot’s logs and can be reviewed via the Dashboard’s Logs panel.
7

Response

The bot returns 200 OK with a JSON body summarising what was applied. Your webhook source (e.g., TradingView, the SignalEditor) gets immediate confirmation.

The SignalsBot’s three small endpoints (besides /webhook)

Beyond the main /webhook POST, the SignalsBot exposes three read-only endpoints — each minimal and inspectable.
A simple “the SignalsBot is running” response. Useful for monitoring tools that just want to know “is this service alive?”. No authentication required (and no sensitive information returned — just a static “running” message).
Confirms database connectivity by running a simple query. Returns 200 OK if the bot can reach the database, 503 Service Unavailable if it can’t.Useful for:
  • Reverse-proxy health checks (Caddy, nginx-proxy-manager).
  • External monitoring (UptimeRobot, Better Uptime, similar).
  • Operator manual checks: curl https://signals.yourdomain.com/health.
Returns the current active trade-pair configuration as the SignalsBot sees it in the database. Read-only — no authentication required (the data is also visible in the Dashboard).Useful when debugging “did my webhook actually update the active pairs?” — call this endpoint after sending a webhook and confirm the value reflects your update.
That’s it — the entire endpoint surface. There’s no admin panel, no signal-history endpoint, no strategy-management endpoint, no exchange-connectivity endpoint. The minimalism is the security feature.

What the SignalsBot does NOT do

The SignalsBot is intentionally one of the smallest services in the stack.
  • Does not subscribe to exchange data. Webhook-only — never reads market data.
  • Does not evaluate strategies. That’s the SignalEditor.
  • Does not place orders. That’s the TradingBot.
  • Does not author strategies. That’s the SignalEditor.
  • Does not produce signals. It only receives them.
  • Does not have an admin panel. Configuration changes happen via the Dashboard or the database, not via the SignalsBot.
  • Does not retry failed webhook deliveries. It’s a receiver, not a sender — outbound retries are the upstream’s responsibility.
  • Does not maintain signal history beyond logs. If you need a queryable signal history, the audit logs are your source.
  • Does not transform payloads. It validates them as-is and applies them as-is. No “interpret the signal” step.
  • Does not call out to external services. Once a webhook arrives, all subsequent action is local.
This deliberate minimalism is the security feature. A small surface is an auditable surface.

Webhook source patterns

Operators send webhooks from a variety of sources. Here’s the standard pattern catalog.
Your locally-running SignalEditor’s strategies POST to the SignalsBot’s /webhook endpoint when triggers fire.Authentication: HMAC-SHA256 (the cryptographically stronger path). The shared secret is set in both the SignalEditor’s environment and the SignalsBot’s environment.Network: traffic stays on your VPS’s internal Docker network. The webhook does not traverse the public internet.This is the most common pattern — almost every operator using the SignalEditor uses this path.
TradingView’s alert system POSTs to your SignalsBot’s /webhook endpoint when a Pine Script alert fires.Authentication: shared-secret token (TradingView doesn’t support HMAC signing of webhook bodies). The secret is included in the alert message body as a secret field.Network: traffic traverses the public internet — TLS is mandatory.Setup: in TradingView’s alert dialog, set the webhook URL to https://signals.yourdomain.com/webhook. In the alert message body, include a JSON shape like:
{
  "secret": "your-shared-secret",
  "pair": "BTCUSDT",
  "mode": 4,
  "canBuy": true
}
TradingView’s free tier supports webhook alerts on the higher subscription levels — check their pricing.
Any script that can do an authenticated HTTPS POST can drive the SignalsBot. Common languages:
  • Bash + curl: simplest. One-liner for ad-hoc operator actions.
  • Python: most common for scheduled scripts (e.g., daily volatility-rank rotation).
  • JavaScript: if you have other JS-based infrastructure.
Example Bash one-liner:
curl -X POST https://signals.yourdomain.com/webhook \
  -H 'Content-Type: application/json' \
  -d '{"secret":"<shared>","pair":"BTCUSDT","mode":4,"canBuy":false}'
Use case: emergency operator action without logging into the Dashboard. Or scheduled automation that depends on data outside the SignalEditor’s reach (news APIs, on-chain data, social-sentiment feeds).
Some commercial signal providers offer webhook delivery. The SignalsBot can accept their webhooks if they support a shared-secret in the body or a custom header.Caveats:
  • Trust assessment first. A signal provider with webhook access can affect your live trading. Vet them carefully.
  • Rate-limit at your reverse proxy as an additional layer. The SignalsBot’s 100/15min is a default; you can be more restrictive.
  • Audit the provider’s signals weekly. If they start sending unexpected payloads, revoke immediately.
Most operators don’t go this route — they prefer to author their own strategies in the SignalEditor.

Setting up TradingView integration

A common operator setup. Step-by-step.
1

Confirm your TradingView plan supports webhooks

Webhook alerts are a paid TradingView feature (Pro and above as of 2026). Confirm before proceeding.
2

Configure your reverse proxy

Put your SignalsBot behind a reverse proxy with a real DNS name and TLS. Caddy is the lowest-friction option:
signals.yourdomain.com {
  reverse_proxy signalsbot:3000
}
Caddy auto-acquires Let’s Encrypt certificates. Once running, https://signals.yourdomain.com/health should return OK.
3

Set the shared secret

Generate a long random shared secret on your VPS:
openssl rand -hex 32
Set the result as the TRADINGVIEW_WEBHOOK_SECRET environment variable in the SignalsBot container. Restart the container.Save this secret in your password manager. You’ll need it in TradingView’s alert configuration.
4

Create a TradingView alert

On TradingView, create an alert on your symbol of choice with your desired condition. In the alert dialog:
  • Webhook URL: https://signals.yourdomain.com/webhook
  • Message body: paste the JSON template you want the alert to send, including the secret field with your shared secret value.
Example for “buy signal”:
{
  "secret": "<your-shared-secret>",
  "pair": "BTCUSDT",
  "mode": 4,
  "canBuy": true
}
5

Test with a one-shot alert

Set up a one-shot alert that fires immediately (e.g., a price-touches-current-price condition). Confirm the SignalsBot’s audit log shows the accepted webhook. Check the Dashboard to confirm the configuration change took effect.
6

Build out your alert pattern

Once one alert works, replicate the pattern for the other alerts you want. Common pattern: paired “buy signal” and “sell signal” alerts on the same condition.

Operational footprint

Resource cost

100–200 MB RAM at idle. Sub-percent CPU. The bot is a lightweight HTTP server with one mutating endpoint and a small connection pool.

Network calls

Inbound on the webhook port (behind your TLS proxy). No outbound to the internet — all writes go to the local database. The audit ground truth is local.

Failure mode

If the SignalsBot crashes, trading is unaffected — the TradingBot continues with its current configuration. New webhook-driven changes pause until the SignalsBot is back.

Startup time

Boots in seconds. Validates that the webhook secret is set; refuses to start otherwise. Validates database connectivity; backs off and retries if not ready.

Audit log size

Each accepted webhook produces ~1 line. Each rejected webhook produces ~1 line. Even at high webhook frequencies, log volume is modest. Forward to your preferred sink for long-term retention.

Internet exposure

The only inbound exposure is the /webhook endpoint. Behind a reverse proxy, configure your firewall to allow only the proxy’s port. The bot itself listens on internal Docker network.

Common questions

No. If you only run pre-built modes (BasicMode, FullBullMarket, etc.), the SignalsBot is optional — those modes are static configuration that doesn’t change unless you change them via the Dashboard.You need the SignalsBot if you’re:
  • Running custom strategies in the SignalEditor (which posts webhooks here)
  • Receiving TradingView alerts
  • Driving configuration from external scripts or third-party signal providers
Most operators turn it on after they start authoring custom strategies.
Put it behind a TLS reverse proxy with a real DNS name (e.g., signals.yourdomain.com). Caddy and nginx-proxy-manager are operator favorites because they automate Let’s Encrypt certificates.In TradingView’s alert creation dialog, set the webhook URL to https://signals.yourdomain.com/webhook, and include the alert message body with your shared secret in the secret field. The SignalsBot will accept the alert if the secret matches.Never expose the raw HTTP port. TradingView’s webhooks travel over the public internet — without TLS, your shared secret is in the clear.
HMAC-SHA256 (the X-Signature header) is cryptographically stronger. The sender signs the raw request body with a shared key; the receiver re-computes the signature and rejects mismatches. An attacker who intercepts the request cannot forge new signed requests without the key.Shared-secret tokens (the X-TRADINGVIEW-TOKEN header or body.secret field) are simpler — the secret is just sent in the request. This is what TradingView supports natively, so unCoded supports it too.For SignalEditor → SignalsBot communication, HMAC is used by default. For TradingView, the shared-secret fallback applies. Both paths use the same secret stored in your environment.
Yes. Any script that can do an authenticated HTTPS POST can drive the SignalsBot. Send a JSON body with your secret plus the fields you want to change, POST to /webhook, and you’re done.Use this for ad-hoc operator actions (e.g., disabling buying for a specific mode in response to news), for integrations with your own analytics, or for bridging from a signal provider that doesn’t natively support webhooks.Operators with light dev backgrounds typically write a few small Bash + curl or Python scripts to wrap common actions (kill switch, mode reset, capital reallocation) — these become a reliable, version-controllable operator toolkit.
The SignalsBot is not idempotent at the request level. If you send enable buying for mode 4 twice in a row, the second one re-applies the same change (which is a no-op in this case) and emits two audit log lines.For ordinary configuration updates, this is fine — applying the same setting twice has no negative effect. For one-shot actions, your sender is responsible for de-duplication.For TradingView, this can occasionally happen during alert retries — TradingView retries failed webhook deliveries. The retries are usually idempotent (same content), so they’re handled gracefully.
Real signal payloads are typically a few hundred bytes — even a complex multi-mode update is well under 5 KB. The 64 KB cap is generous enough for any legitimate use, while protecting the bot from memory-flood attacks where a malicious source streams gigabytes of body.If you have a legitimate signal that exceeds 64 KB (very unusual), contact support — the cap is configurable but rarely needs to change.
Every successful webhook produces a log line like:
[AUDIT] 203.0.113.45 tradepairs_update added=BTCUSDT,ETHUSDT removed=XRPUSDT
[AUDIT] 203.0.113.45 mode_update mode=4 changes={canBuy:false,sellPercentages:[0.5,1,2,3,4,5,7]}
Every rejection logs with the precise reason:
[REJECT] 198.51.100.7 reason=invalid_hmac
[REJECT] 192.0.2.99 reason=mode_validation_failed details=sellPercentages.length(8)!=buySplits(7)
The Dashboard’s Logs panel surfaces these for review. For long-term retention, configure log shipping to your preferred sink.
Yes — and you should, periodically. Rotation pattern:
  1. Generate a new secret: openssl rand -hex 32.
  2. Update the SignalsBot’s environment variable. Restart the container.
  3. Update every upstream sender (SignalEditor, TradingView alerts, custom scripts) to use the new secret.
  4. Verify a test webhook works.
  5. The old secret is dead.
Operator habit: rotate quarterly. Mark the rotation in your operator log so you can correlate “did anything start failing right after the rotation?” if something does.
Yes — though most operators don’t need to. A single SignalsBot can handle webhooks from many sources (SignalEditor + TradingView + custom scripts) all in parallel.Reasons to run multiple SignalsBots:
  • Different webhook secrets per source (rotate independently).
  • Different rate limits per source (high-rate strategy source vs low-rate operator script).
  • Geographic distribution (a SignalsBot near each VPS).
The pattern: each SignalsBot is configured with its own secret, points at the same shared database, listens on its own port (each with its own reverse-proxy domain).
Database writes are transactional. If a webhook starts a write and the connection drops mid-flight, the database transaction either commits fully or rolls back fully — no half-state.The webhook caller may not know the outcome (it never got a response). The standard pattern: the caller retries on connection failure. The SignalsBot accepts the retry, sees the change is already applied (idempotent at the data level for most operations), and acknowledges.
If your reverse proxy crashes, inbound webhooks fail at the proxy layer — the SignalsBot never sees them. Your trading is unaffected (the TradingBot continues with current config), but new webhook-driven configuration changes don’t propagate.Most reverse proxies (Caddy, nginx-proxy-manager) restart automatically. Total outage is usually seconds.For high-availability, run two reverse proxies behind a load balancer. Most operators don’t bother — single-proxy is sufficient for typical workloads.

Best practices

  • Keep your webhook secret long and random — generate it with openssl rand -hex 32 or similar.
  • Rotate the secret periodically — once a quarter is reasonable. Rotation requires updating both the SignalsBot’s environment and every upstream sender’s configuration.
  • Always use HTTPS — never expose the raw HTTP port to the public internet.
  • Watch the audit log weekly — confirm only your expected sources are sending webhooks. Unknown source IPs are a flag worth investigating.
  • Use HMAC where possible — the SignalEditor → SignalsBot path uses it by default. For TradingView, the shared-secret fallback is acceptable but cryptographically weaker.
  • Don’t run the SignalsBot on the same VPS as untrusted services — it’s the most security-sensitive surface; keep its neighborhood clean.
  • Set up monitoring on the /health endpoint — UptimeRobot, Better Uptime, or your existing monitoring. Alert on any downtime.
  • Allow-list source IPs at the reverse proxy if you can — TradingView’s IPs are public; your SignalEditor’s IP is fixed. Restrict to known sources.
  • Test webhook integrations during quiet hours — a misconfigured webhook during a busy market session is harder to debug.
  • Forward audit logs to a long-term sink — for compliance and post-incident analysis.
  • Document your webhook secret rotation cadence in your operator runbook so you don’t drift.

What’s next

SignalEditor

The upstream that authors strategies and posts the webhooks the SignalsBot receives.

TradingBot

The downstream that picks up the configuration changes the SignalsBot writes.

Architecture

Where the SignalsBot sits in the overall network and security model.

Security Philosophy

Why the SignalsBot is intentionally tiny and audit-clear.

Dashboard

Where you see the audit log and the resulting configuration changes.

Strategy Recipes

Worked examples of strategies that drive the SignalsBot.
Last modified on May 3, 2026