# Traderspost Webhook

> Send trade signals to TradersPost via webhook from a Python backend, including the canonical buy/sell/exit payload shapes and an APScheduler cron pattern for live strategies. Use when the user wants to automate orders via TradersPost, schedule recurring signal evaluation, or build a "Go Live" toggle for a strategy.

- Skill: `traderspost/traderspost-webhook` (Agent Skill)
- Install (CLI): `npx skillmds@latest add traderspost/traderspost-webhook`
- Raw SKILL.md: https://api.skillmd.com/api/skills/traderspost/traderspost-webhook/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: traderspost (https://skillmd.com/u/traderspost)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/traderspost/traderspost-webhook

---


# traderspost-webhook

[TradersPost](https://traderspost.io) executes trades when it receives a JSON POST to your strategy's webhook URL. This skill covers the payload shapes, retry semantics, and how to drive webhooks on a cron from APScheduler.

## When to use

Trigger this skill when the user wants to:
- POST a buy/sell/exit signal to TradersPost from a backend service
- Build a "Go Live" toggle on a strategy that fires webhooks on a schedule
- Test a webhook with a one-off payload from a dev UI
- Convert a strategy's `signal()` function output into the right JSON shape

## Webhook URL

Each TradersPost strategy has its own URL:

```
https://webhooks.traderspost.io/trading/webhook/<strategy-id>
```

Find it under the strategy's "Webhooks" tab in the TradersPost dashboard. Treat it like a secret — anyone with the URL can place orders.

## Payload shapes

The webhook listens for JSON. Minimum required fields:

```json
{ "action": "buy",  "ticker": "AAPL" }
{ "action": "sell", "ticker": "AAPL" }
{ "action": "exit", "ticker": "AAPL" }
```

Optional fields:

```json
{
  "action": "buy",
  "ticker": "AAPL",
  "quantity": 10,             // shares — omit to use strategy's default sizing
  "price": 234.56,            // limit price — omit for market
  "sentiment": "bullish",     // freeform; surfaces in TradersPost UI
  "signalPrice": 234.50,      // price at which the signal was generated
  "stopLoss": { "type": "stop_loss", "stopPrice": 230.00 },
  "takeProfit": { "limitPrice": 240.00 }
}
```

For crypto, use the dotted pair format TradersPost expects:

```json
{ "action": "buy", "ticker": "BTC.USD" }
```

## Posting from Python

```python
import httpx


async def post_webhook(url: str, payload: dict) -> dict:
    """Returns {ok, status, body}. Raises on transport errors only."""
    async with httpx.AsyncClient(timeout=10.0) as client:
        r = await client.post(url, json=payload)
        return {"ok": r.is_success, "status": r.status_code, "body": r.text[:500]}


def buy(ticker: str, quantity: float | None = None) -> dict:
    out = {"action": "buy", "ticker": ticker}
    if quantity is not None:
        out["quantity"] = quantity
    return out


def sell(ticker: str, quantity: float | None = None) -> dict:
    out = {"action": "sell", "ticker": ticker}
    if quantity is not None:
        out["quantity"] = quantity
    return out


def exit_(ticker: str) -> dict:
    return {"action": "exit", "ticker": ticker}
```

TradersPost responds with 2xx on accepted signals. Treat non-2xx as a soft error and log — do **not** auto-retry (you'll double-fire on a flapping connection). The TradersPost dashboard shows the full audit trail of received webhooks.

## Scheduling with APScheduler

For a "Go Live" toggle that runs a strategy on a cron:

```python
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
import logging

log = logging.getLogger("scheduler")
scheduler = AsyncIOScheduler()
scheduler.start()
_jobs: dict[str, str] = {}  # strategy_name → job_id


async def _tick(name: str, webhook: str, signal_fn) -> None:
    payload = signal_fn()  # your strategy returns a dict or None
    if not payload:
        return
    result = await post_webhook(webhook, payload)
    log.info("%s → %s", name, result)


def start_live(name: str, cron: str, webhook: str, signal_fn) -> str:
    """`cron` follows standard cron syntax — '*/5 9-15 * * 1-5' = every 5 min, weekdays, market hours."""
    job = scheduler.add_job(
        _tick, CronTrigger.from_crontab(cron),
        kwargs={"name": name, "webhook": webhook, "signal_fn": signal_fn},
        id=f"strategy-{name}", replace_existing=True,
    )
    _jobs[name] = job.id
    return job.id


def stop_live(name: str) -> bool:
    job_id = _jobs.pop(name, None)
    if job_id:
        scheduler.remove_job(job_id)
        return True
    return False
```

Cron tips:
- Market hours weekdays: `*/5 9-15 * * 1-5` (every 5 min, 9am–3:55pm)
- End of day: `30 15 * * 1-5` (3:30pm weekdays)
- Pre-market: `0 8 * * 1-5`
- Crypto 24/7: `*/15 * * * *`

APScheduler runs in process — if you restart the API, scheduled jobs are lost. For durable scheduling, persist the job specs to a SQLite/JSON store on `start_live` and re-register them on FastAPI startup.

## Testing without firing real orders

Use [webhook.site](https://webhook.site) for a free disposable inbox during development. Set `TRADERSPOST_WEBHOOK_URL` to that URL, fire a test, and inspect the received JSON in the browser before pointing at a real strategy.

## Reference

In command-dash:
- `apps/api/app/signals/traderspost.py` — payload helpers + `post_webhook`
- `apps/api/app/signals/scheduler.py` — APScheduler + strategy loading
- `apps/api/app/signals/routes.py` — `POST /api/strategies/{name}/live`, `POST /api/strategies/test-webhook`

