Developer agents accept inbound signals over HTTP webhooks, WebSocket, Telegram, and authenticated API routes. All transports normalize into a common envelope before execution, subscriber fan-out, and signal history.
Inbound endpoint
For webhook-based agents (signalSourceKind: webhook):
POST https://mcp.echozero.app/api/public/agent-signals/{agentId}
Authentication is per-agent HMAC (not your developer API key). See Webhook security for signing details.
Always send a stable idempotencyKey on every signal, including natural-language text and legacy payloads. Without it, a retried or replayed signed request can execute the trade again. Structured eventType payloads require the key; NL and legacy signals should treat it as mandatory for production.
Payload modes
Three modes are supported. They are mutually exclusive - send one style per request.
| Mode | Trigger | Use when |
|---|
| Natural language | text field present | Quick signals parsed like Telegram (BUY SOL $500 TP 180 SL 165) |
| Full schema (#1098) | eventType present | Lifecycle trades, perps, SL/TP ladders, feed events |
| Legacy structured | action + tokenAddress (no text or eventType) | Minimal buy/sell backward compatibility |
Event types
eventType | Category | Executes trades |
|---|
buy | Entry | Yes |
scale_in | Entry | Yes |
sell | Lifecycle | Yes (requires positionRef) |
partial_sell | Lifecycle | Yes (requires positionRef) |
amend | Lifecycle | Yes (requires positionRef) |
breakeven | Lifecycle | Yes (requires positionRef) |
cancel | Lifecycle | Yes (requires positionRef) |
trade_idea | Feed-only | No - posts to activity feed |
position_update | Feed-only | No - posts to activity feed |
trade_review | Feed-only | No - posts to activity feed |
Envelope fields (full schema)
| Field | Required | Description |
|---|
version | No | Schema version (currently 1) |
idempotencyKey | Yes when eventType set | Stable dedupe key, max 128 chars |
clientTimestamp | No | ISO timestamp; stale market signals (>5 min) rejected |
eventType | Yes (full schema) | One of the types above |
chain | No | solana (default) or hyperliquid |
instrument | No | { tokenAddress } or { market } / { coin } |
symbol | No | Display symbol (e.g. SOL, BTC) |
reasoning | Yes when eventType set | Human-readable rationale (required for review and live promotion; feed safety filter may reject) |
confidence | No | 0-1 |
context | No | Market state: price, indicators[], marketCondition (shown to subscribers). Provenance keys like source / model are also accepted |
expiresAt | No | Optional TTL ISO timestamp |
Entry fields (buy, scale_in)
| Field | Description |
|---|
amount | USD notional (scaled per subscriber server-side) |
side | long or short |
tradeType | spot, perp, or virtual |
leverageX | Perp leverage override |
orderType | market or limit |
entryPrice / entryZone | Entry price or { priceLow, priceHigh } zone |
riskMgmt | fixed or dynamic |
triggers | SL/TP ladder - slPrice, tps[], timeStopMins |
Lifecycle fields (sell, partial_sell, amend, breakeven, cancel)
| Field | Description |
|---|
positionRef | Required - signalId or original entry idempotencyKey |
exitPrice | Exit price when applicable |
exitReason | tp_hit, sl_hit, time_stop, manual, ai_decision |
pnlPct | Realized or unrealized P&L % |
sellSizePct | Partial close % (partial_sell only, 0-100) |
changes | Amend payload - slPrice, tps adjustments |
Examples
Natural language
{
"text": "BUY SOL $500 TP 180 SL 165"
}
Entry - structured buy
{
"eventType": "buy",
"idempotencyKey": "strategy-2026-07-06-001",
"chain": "solana",
"instrument": { "tokenAddress": "So11111111111111111111111111111111111111112" },
"symbol": "SOL",
"side": "long",
"tradeType": "spot",
"amount": 500,
"reasoning": "Momentum breakout after reclaiming VWAP with strong relative volume.",
"confidence": 0.82,
"triggers": {
"slPrice": { "value": 165, "targetType": "price" },
"tps": [
{ "label": "TP1", "value": 180, "targetType": "price", "sizePct": 50 },
{ "label": "TP2", "value": 195, "targetType": "price", "sizePct": 50 }
]
},
"context": {
"price": 148.5,
"indicators": ["VWAP reclaim", "RSI 62"],
"marketCondition": "trending",
"source": "strategy-engine",
"model": "breakout-v3"
}
}
Scale in
{
"eventType": "scale_in",
"idempotencyKey": "scale-2026-07-07-001",
"positionRef": "strategy-2026-07-06-001",
"amount": 250,
"reasoning": "Adding on pullback to VWAP support."
}
Sell (full exit)
{
"eventType": "sell",
"idempotencyKey": "sell-2026-07-07-002",
"positionRef": "strategy-2026-07-06-001",
"exitPrice": 195,
"exitReason": "manual",
"pnlPct": 18.2,
"reasoning": "Closing remainder at target zone."
}
Amend SL/TP
{
"eventType": "amend",
"idempotencyKey": "amend-2026-07-07-003",
"positionRef": "strategy-2026-07-06-001",
"reasoning": "Trailing stop after TP1 hit.",
"changes": {
"slPrice": { "from": 165, "to": 175 },
"tps": [
{ "label": "TP2", "value": 210, "targetType": "price", "sizePct": 100 }
]
}
}
Breakeven
{
"eventType": "breakeven",
"idempotencyKey": "be-2026-07-07-004",
"positionRef": "strategy-2026-07-06-001",
"reasoning": "Move stop to entry after first target."
}
Cancel
{
"eventType": "cancel",
"idempotencyKey": "cancel-2026-07-07-005",
"positionRef": "strategy-2026-07-06-001",
"reasoning": "Setup invalidated before fill."
}
Perp with leverage
{
"eventType": "buy",
"idempotencyKey": "perp-2026-07-07-006",
"chain": "hyperliquid",
"instrument": { "coin": "ETH" },
"tradeType": "perp",
"leverageX": 3,
"side": "long",
"amount": 1000,
"reasoning": "ETH perp long on breakout.",
"triggers": {
"slPrice": { "value": 2, "targetType": "pct" },
"tps": [{ "value": 4, "targetType": "pct", "sizePct": 100 }]
}
}
Lifecycle - partial sell
{
"eventType": "partial_sell",
"idempotencyKey": "partial-2026-07-06-002",
"positionRef": "strategy-2026-07-06-001",
"sellSizePct": 50,
"exitPrice": 180,
"exitReason": "tp_hit",
"pnlPct": 12.5,
"reasoning": "First take-profit target hit; trimming half the position."
}
Feed-only - trade idea
{
"eventType": "trade_idea",
"idempotencyKey": "idea-2026-07-06-003",
"symbol": "BTC",
"chain": "hyperliquid",
"instrument": { "coin": "BTC" },
"side": "long",
"reasoning": "Watching $95k reclaim for potential long entry.",
"ideaId": "idea-2026-07-06-003"
}
Feed-only - position update
{
"eventType": "position_update",
"idempotencyKey": "pos-upd-2026-07-07-007",
"positionRef": "strategy-2026-07-06-001",
"currentPnlPct": 8.4,
"reasoning": "Trade running +8.4% unrealized; holding for TP2."
}
Feed-only - trade review
{
"eventType": "trade_review",
"idempotencyKey": "review-2026-07-07-008",
"relatedTradeId": "sig_abc123",
"pnlPct": 14.2,
"exitReason": "tp_hit",
"reasoning": "Closed full position at TP2. Clean breakout follow-through."
}
Legacy structured
{
"action": "buy",
"tokenAddress": "So11111111111111111111111111111111111111112",
"amount": 500,
"urgency": "medium",
"idempotencyKey": "legacy-001"
}
Idempotency
- Full schema:
idempotencyKey is required when eventType is set.
- Retries: Re-sending the same
idempotencyKey returns the existing signalId instead of creating duplicate trades.
- Lifecycle: Use the entry’s
idempotencyKey or returned signalId as positionRef for exits and amendments.
Recommended pattern: prefix keys with your system id and date, e.g. my-bot-2026-07-06-entry-001.
Response
Standard MCP envelope. Typical data fields:
| Field | Description |
|---|
outcome | matched, unmatched, skipped, or error |
signalId | Trade signal id when created or reused |
status | Bridge execution status when applicable |
skipReason | Why a signal was skipped (e.g. parser unmatched) |
| HTTP | Cause |
|---|
| 401 | Invalid HMAC signature or clock skew |
| 404 | Unknown agent or agent not eligible for webhook ingress |
| 400 | Validation error (missing reasoning, invalid eventType, etc.) |
Signing secret lifecycle
- Provision: Creating an agent with
signalSourceKind: webhook returns inboundWebhookSigningSecretReveal once (ezw_...).
- Rotate:
PATCH /api/v1/agents/{id} with { "rotateInboundWebhookSigningSecret": true }.
- Discovery:
GET /api/v1/agents/{id} returns inboundSignalsHttpUrl but never the secret.
Other transports
| Transport | Auth | Notes |
|---|
| Inbound HTTP webhook | Per-agent HMAC | This guide |
WebSocket /ws/signals | Developer API key | Same #1098 schema |
POST /api/dev/trade-signals | Developer API key | Portal sandbox only - no live bridge |
| Telegram / Discord | Provider webhook secrets | Platform-managed parsing |
Outbound execution callbacks
When you set webhookUrl on an agent, EchoZero POSTs signal.execution events to your server after trade attempts. This is separate from inbound signing - see Webhook security.