API Reference
Webhooks
Events

Webhook Events

Complete reference for all webhook event types and payloads

Event Types

EventTrigger
intent.createdTrade Intent successfully created
intent.pendingOrder submitted to broker
intent.filledOrder fully executed
intent.partially_filledOrder partially executed
intent.canceledOrder canceled
intent.rejectedBroker rejected the order
intent.failedRouting or validation failed

Event Structure

All webhook events follow this structure:

{
  "type": "event_type",
  "id": "evt_unique_id",
  "created_at": "2025-11-30T10:30:00Z",
  "data": {
    // Trade Intent object
  }
}

intent.created

Sent when a Trade Intent is successfully created.

{
  "type": "intent.created",
  "id": "evt_abc123",
  "created_at": "2025-11-30T10:29:59Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_001",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 10,
    "order_type": "market",
    "status": "pending",
    "broker_id": "alpaca_abc123",
    "orders": [],
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:29:59Z"
  }
}

Use case: Log intent creation, update UI

intent.pending

Sent when the order is submitted to the broker and awaiting execution.

{
  "type": "intent.pending",
  "id": "evt_def456",
  "created_at": "2025-11-30T10:30:00Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_001",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 10,
    "order_type": "market",
    "status": "pending",
    "broker_id": "alpaca_abc123",
    "orders": [
      {
        "id": "660e8400-e29b-41d4-a716-446655440000",
        "broker_order_id": "alpaca_order_123",
        "status": "pending",
        "fills": [],
        "created_at": "2025-11-30T10:30:00Z",
        "updated_at": "2025-11-30T10:30:00Z"
      }
    ],
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:30:00Z"
  }
}

Use case: Show "Order submitted" status to user

intent.filled

Sent when the order is fully executed.

{
  "type": "intent.filled",
  "id": "evt_ghi789",
  "created_at": "2025-11-30T10:30:05Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_001",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 10,
    "order_type": "market",
    "status": "filled",
    "broker_id": "alpaca_abc123",
    "orders": [
      {
        "id": "660e8400-e29b-41d4-a716-446655440000",
        "broker_order_id": "alpaca_order_123",
        "status": "filled",
        "fills": [
          {
            "quantity": 10,
            "price": 175.50,
            "filled_at": "2025-11-30T10:30:05Z"
          }
        ],
        "created_at": "2025-11-30T10:30:00Z",
        "updated_at": "2025-11-30T10:30:05Z"
      }
    ],
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:30:05Z"
  }
}

Use case: Update portfolio, send notification, log fill price

intent.partially_filled

Sent when the order is partially executed.

{
  "type": "intent.partially_filled",
  "id": "evt_jkl012",
  "created_at": "2025-11-30T10:30:10Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_002",
    "symbol": "TSLA",
    "side": "buy",
    "quantity": 100,
    "order_type": "limit",
    "limit_price": 250.00,
    "status": "partially_filled",
    "broker_id": "alpaca_abc123",
    "orders": [
      {
        "id": "770e8400-e29b-41d4-a716-446655440000",
        "broker_order_id": "alpaca_order_456",
        "status": "partially_filled",
        "fills": [
          {
            "quantity": 50,
            "price": 250.00,
            "filled_at": "2025-11-30T10:30:10Z"
          }
        ],
        "created_at": "2025-11-30T10:30:00Z",
        "updated_at": "2025-11-30T10:30:10Z"
      }
    ],
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:30:10Z"
  }
}

Use case: Track partial fills, update UI with progress

intent.canceled

Sent when the order is canceled before full execution.

{
  "type": "intent.canceled",
  "id": "evt_mno345",
  "created_at": "2025-11-30T10:35:00Z",
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_003",
    "symbol": "NVDA",
    "side": "buy",
    "quantity": 20,
    "order_type": "limit",
    "limit_price": 500.00,
    "status": "canceled",
    "broker_id": "alpaca_abc123",
    "orders": [
      {
        "id": "990e8400-e29b-41d4-a716-446655440000",
        "broker_order_id": "alpaca_order_789",
        "status": "canceled",
        "fills": [],
        "created_at": "2025-11-30T10:30:00Z",
        "updated_at": "2025-11-30T10:35:00Z"
      }
    ],
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:35:00Z"
  }
}

Use case: Notify user, log cancellation

intent.rejected

Sent when the broker rejects the order.

{
  "type": "intent.rejected",
  "id": "evt_pqr678",
  "created_at": "2025-11-30T10:30:02Z",
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_004",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 1000,
    "order_type": "market",
    "status": "rejected",
    "broker_id": "alpaca_abc123",
    "orders": [
      {
        "id": "bb0e8400-e29b-41d4-a716-446655440000",
        "broker_order_id": "alpaca_order_000",
        "status": "rejected",
        "fills": [],
        "created_at": "2025-11-30T10:30:00Z",
        "updated_at": "2025-11-30T10:30:02Z"
      }
    ],
    "error": {
      "code": "insufficient_funds",
      "message": "Insufficient buying power"
    },
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:30:02Z"
  }
}

Use case: Alert user, log error, potentially retry

Common rejection codes:

  • insufficient_funds - Not enough buying power
  • invalid_symbol - Symbol not found
  • market_closed - Market is closed
  • halted - Trading halted for symbol

intent.failed

Sent when routing or validation fails (before reaching broker).

{
  "type": "intent.failed",
  "id": "evt_stu901",
  "created_at": "2025-11-30T10:30:01Z",
  "data": {
    "id": "cc0e8400-e29b-41d4-a716-446655440000",
    "intent_id": "trade_005",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 10,
    "order_type": "market",
    "status": "failed",
    "broker_id": "invalid_broker",
    "orders": [],
    "error": {
      "code": "broker_not_found",
      "message": "Broker connection not found"
    },
    "created_at": "2025-11-30T10:29:59Z",
    "updated_at": "2025-11-30T10:30:01Z"
  }
}

Use case: Alert user, log error, check configuration

Common failure codes:

  • broker_not_found - Invalid broker_id
  • broker_unauthorized - OAuth token expired
  • validation_error - Invalid parameters

Handling Events

Python Example

from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
import time
 
app = FastAPI()
WEBHOOK_SECRET = "your_hex_encoded_secret"  # 64 character hex string
TIMESTAMP_TOLERANCE = 300  # 5 minutes
 
@app.post("/webhooks/sniperoute")
async def handle_webhook(request: Request):
    # Get headers
    signature_header = request.headers.get("sr-signature")
    timestamp = request.headers.get("sr-timestamp")
    event_id = request.headers.get("sr-event-id")
 
    if not signature_header or not timestamp:
        raise HTTPException(status_code=401, detail="Missing required headers")
 
    # Check timestamp
    try:
        ts = int(timestamp)
        if abs(time.time() - ts) > TIMESTAMP_TOLERANCE:
            raise HTTPException(status_code=401, detail="Timestamp too old")
    except ValueError:
        raise HTTPException(status_code=401, detail="Invalid timestamp")
 
    # Parse signature (format: "v1=...")
    if not signature_header.startswith("v1="):
        raise HTTPException(status_code=401, detail="Invalid signature format")
    signature = signature_header[3:]
 
    body = await request.body()
    message = f"{timestamp}.{body.decode()}"
 
    expected_sig = hmac.new(
        bytes.fromhex(WEBHOOK_SECRET),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
 
    if not hmac.compare_digest(signature, expected_sig):
        raise HTTPException(status_code=401, detail="Invalid signature")
 
    # Parse event
    event = await request.json()
    event_type = event["type"]
    data = event["data"]
 
    # Handle events
    if event_type == "intent.filled":
        # Update database
        await db.execute(
            "UPDATE trades SET status = 'filled', avg_price = :price WHERE intent_id = :id",
            {"price": data["orders"][0]["fills"][0]["price"], "id": data["intent_id"]}
        )
 
        # Send notification
        await notify_user(f"Order filled: {data['symbol']}")
 
    elif event_type == "intent.rejected":
        # Log error
        error_msg = data.get("error", {}).get("message", "Unknown error")
        await log_error(f"Order rejected: {error_msg}")
 
        # Notify user
        await notify_user(f"Order rejected: {error_msg}")
 
    elif event_type == "intent.partially_filled":
        # Track partial fill
        fills = data["orders"][0]["fills"]
        filled_qty = sum(f["quantity"] for f in fills)
        await db.execute(
            "UPDATE trades SET filled_quantity = :qty WHERE intent_id = :id",
            {"qty": filled_qty, "id": data["intent_id"]}
        )
 
    return {"status": "received"}

Node.js Example

app.post('/webhooks/sniperoute', async (req, res) => {
  const event = req.body;
 
  switch (event.type) {
    case 'intent.filled':
      console.log(`Order filled: ${event.data.intent_id}`);
      // Update database, send notification
      break;
 
    case 'intent.rejected':
      console.log(`Order rejected: ${event.data.error.message}`);
      // Log error, notify user
      break;
 
    case 'intent.partially_filled':
      const fills = event.data.orders[0].fills;
      const filledQty = fills.reduce((sum, f) => sum + f.quantity, 0);
      console.log(`Partially filled: ${filledQty}/${event.data.quantity}`);
      break;
  }
 
  res.status(200).send({ status: 'received' });
});

Next Steps