@pocketsignals/broker-client
v2.0.0
Published
Webhook server for receiving PocketSignals trading signals and dispatch client
Maintainers
Readme
@pocketsignals/broker-client
Webhook server and dispatch client for receiving PocketSignals trading signals.
Contract Version
v2.0.0 - Push/Webhook Architecture
v2.0.0 Breaking Changes
- Architecture Change: PocketSignals now PUSHES signals to your webhook server
BrokerPollingClientis deprecated - useWebhookServerinstead- New push model is more efficient and lower latency
Installation
npm install @pocketsignals/broker-clientQuick Start - Webhook Server (Recommended)
The webhook server receives push signals from PocketSignals:
import { WebhookServer } from '@pocketsignals/broker-client';
const server = new WebhookServer({
config: {
port: 8080,
serviceKey: process.env.POCKETSIGNALS_SERVICE_KEY!,
basePath: '/broker/signals', // Optional, this is the default
},
handlers: {
onEntry: async (signal) => {
console.log('Entry signal received:', {
symbol: signal.symbol,
direction: signal.direction,
price: signal.price,
tsid: signal.tsid,
});
// Execute your trade logic here...
const result = await executeAlpacaOrder(signal);
return {
success: true,
signalId: signal.signalId,
accepted: true,
};
},
onExit: async (signal) => {
console.log('Exit signal received:', {
symbol: signal.symbol,
exitReason: signal.exitReason,
tsid: signal.tsid,
});
// Close your position here...
const result = await closeAlpacaPosition(signal);
return {
success: true,
signalId: signal.signalId,
accepted: true,
};
},
onStatus: async (signal) => {
console.log('Status update:', signal.status);
return { success: true };
},
onError: (error, payload) => {
console.error('Handler error:', error, payload);
},
},
});
// Start the server
await server.start();
console.log(`Webhook server running on port ${server.getPort()}`);
// Check metrics
const metrics = server.getMetrics();
console.log('Signals received:', metrics.signalsReceived);
// Graceful shutdown
process.on('SIGTERM', async () => {
await server.stop();
});Webhook Endpoints
PocketSignals will push signals to:
| Endpoint | Description |
|----------|-------------|
| POST /broker/signals/entry | Entry signals (buy/call) |
| POST /broker/signals/exit | Exit signals (sell/close) |
| POST /broker/signals/status | Status updates |
Health endpoints:
| Endpoint | Description |
|----------|-------------|
| GET /health | Health check |
| GET /ready | Readiness check |
| GET /metrics | Server metrics |
Entry Signal Payload
interface EntrySignalWebhook {
signalType: 'ENTRY';
signalId: string;
idempotencyKey: string;
tsid: string;
userId: string;
symbol: string;
assetType: 'options' | 'crypto';
direction: 'BUY' | 'SELL' | 'CALL' | 'PUT';
quantity: number;
price: number;
confidence: number;
rationale: string;
contractParams?: {
strike: number;
expiry: string;
type: 'CALL' | 'PUT';
contractSymbol?: string;
};
riskManagement?: RiskManagement;
contractVersion: string;
timestamp: number;
}Exit Signal Payload
interface ExitSignalWebhook {
signalType: 'EXIT';
signalId: string;
idempotencyKey: string;
tsid: string;
userId: string;
symbol: string;
assetType: 'options' | 'crypto';
quantity: number;
exitReason: 'AI_COACH' | 'STOP_LOSS' | 'TAKE_PROFIT' | 'CIRCUIT_BREAKER' | 'MANUAL' | 'EXPIRY';
riskManagement?: RiskManagement;
contractVersion: string;
timestamp: number;
}HMAC Authentication
All incoming requests include HMAC signatures for security:
| Header | Description |
|--------|-------------|
| X-Broker-Signature | HMAC-SHA256 signature of <timestamp>.<body> |
| X-Broker-Timestamp | Unix timestamp in milliseconds |
| Content-Type | Must be application/json |
Security Guarantees
- Timestamp validation: ±30 second drift tolerance (configurable)
- Replay protection: Duplicate signature+timestamp+body combinations are blocked
- Timing-safe comparison: Constant-time signature verification to prevent timing attacks
Error Codes
| HTTP Code | Description |
|-----------|-------------|
| 401 | Missing or invalid signature |
| 408 | Timestamp expired (outside ±30s window) |
| 409 | Duplicate request (replay attack blocked) |
The WebhookServer automatically verifies signatures. For custom verification:
import { WebhookHmacVerifier } from '@pocketsignals/broker-client';
const verifier = new WebhookHmacVerifier({
serviceKey: process.env.POCKETSIGNALS_SERVICE_KEY!,
maxTimestampDriftMs: 30000, // 30 seconds (default, contract-compliant)
});
const result = verifier.verify(
req.headers['x-broker-signature'],
req.headers['x-broker-timestamp'],
requestBody
);
if (!result.valid) {
console.error('Auth failed:', result.error);
}Idempotency / Duplicate Suppression
The WebhookServer includes built-in duplicate detection using idempotency keys.
For single-instance deployments (default):
import { InMemoryIdempotencyStorage } from '@pocketsignals/broker-client';
const storage = new InMemoryIdempotencyStorage({
defaultTtlMs: 3600000, // 1 hour
});For multi-instance deployments with Redis:
import Redis from 'ioredis';
import { RedisIdempotencyStorage } from '@pocketsignals/broker-client';
const redis = new Redis(process.env.REDIS_URL);
const storage = new RedisIdempotencyStorage(redis, {
prefix: 'ps:idempotency:',
defaultTtlMs: 3600000,
});
const server = new WebhookServer({
config: { port: 8080, serviceKey: '...' },
handlers: { ... },
idempotencyStorage: storage,
});Environment Variables
| Variable | Description | Required |
|----------|-------------|----------|
| POCKETSIGNALS_SERVICE_KEY | HMAC secret for signature verification | Yes |
Response Format
Handlers should return a WebhookResponse:
interface WebhookResponse {
success: boolean;
signalId?: string;
accepted?: boolean; // True if signal was acted upon
duplicate?: boolean; // True if duplicate was detected
error?: string; // Error message if failed
retryAfterMs?: number; // Request retry after this delay
}Return codes:
200- Signal processed successfully400- Bad request / handler rejected401- Authentication failed408- Timestamp too old409- Duplicate request503- Temporary failure (retry)
Retry Semantics
PocketSignals will retry failed webhook deliveries with exponential backoff:
| Attempt | Delay | |---------|-------| | 1 | Immediate | | 2 | 5 seconds | | 3 | 15 seconds | | 4 | 60 seconds | | 5 | 300 seconds |
Return 503 Service Unavailable with optional retryAfterMs in response body to request custom retry timing.
Production Requirements
- TLS: Webhook endpoints MUST be served over HTTPS in production
- Timeouts: Handler execution should complete within 10 seconds
- Idempotency: Use Redis storage for multi-instance deployments
- Monitoring: Use
/metricsendpoint for observability
Migration from v1.x (Polling Client)
// OLD (deprecated) - Polling for commands
import { BrokerPollingClient } from '@pocketsignals/broker-client';
const client = new BrokerPollingClient({ ... });
while (true) {
const result = await client.pollCommands();
// Process commands...
await sleep(5000);
}
// NEW (v2.0.0) - Receive pushed signals
import { WebhookServer } from '@pocketsignals/broker-client';
const server = new WebhookServer({
config: { port: 8080, serviceKey: '...' },
handlers: {
onEntry: async (signal) => { /* handle entry */ },
onExit: async (signal) => { /* handle exit */ },
},
});
await server.start();Dispatch Client (PocketSignals Internal Use)
For PocketSignals to push signals to Trade Automator:
import axios from 'axios';
import { BrokerDispatchClient } from '@pocketsignals/broker-client';
const client = new BrokerDispatchClient({
config: {
enabled: true,
brokerBaseUrl: process.env.BROKER_BASE_URL!,
serviceKey: process.env.BROKER_SERVICE_KEY!,
callbackUrl: process.env.BROKER_CALLBACK_URL!,
environment: 'production',
},
axios: axios.create(),
logger: console,
});
const result = await client.dispatchSignal({ ... });API Reference
WebhookServer
class WebhookServer {
constructor(options: WebhookServerOptions);
start(): Promise<void>;
stop(): Promise<void>;
isRunning(): boolean;
getPort(): number;
getMetrics(): WebhookMetrics;
}WebhookHmacVerifier
class WebhookHmacVerifier {
constructor(config: { serviceKey: string; maxTimestampDriftMs?: number });
verify(signature: string, timestamp: string, body: string): HmacVerifyResult;
sign(timestamp: number, body: string): string;
stop(): void;
}InMemoryIdempotencyStorage
class InMemoryIdempotencyStorage implements IdempotencyStorage {
constructor(options?: { defaultTtlMs?: number; cleanupIntervalMs?: number });
has(key: string): Promise<boolean>;
set(key: string, ttlMs?: number): Promise<void>;
delete(key: string): Promise<void>;
size(): number;
stop(): void;
}Version Compatibility
| Package Version | Contract Version | Architecture | |-----------------|------------------|--------------| | 2.0.x | 2.0.0 | Push/Webhook (recommended) | | 1.4.x | 1.4.0 | Polling (deprecated) | | 1.3.x | 1.3.0 | Polling |
License
MIT
