Elite Plan

Webhook Documentation

BetPal can push real-time event notifications to any HTTP endpoint you control. Use webhooks to trigger Slack alerts, update spreadsheets, or build custom trading dashboards.

Overview

When a notable event occurs — a bet fires, an order fails, your balance drops below a threshold — BetPal sends a POST request to your configured endpoint with a JSON body describing the event.

Webhooks are available on the Elite plan and are configured at Dashboard → Webhooks.

Base payload structure
{
  "event":     "bet.placed",
  "timestamp": "2025-03-09T18:00:00+00:00",
  "data": {
    ...event-specific fields...
  }
}

Verifying Signatures

Every request includes an X-BetPal-Signature header containing an HMAC-SHA256 digest of the raw request body, signed with your endpoint's secret key.

X-BetPal-Signature: sha256=<hex-digest>

Always verify this signature before processing the event to ensure it originated from BetPal and wasn't tampered with.

import hashlib, hmac

def verify_signature(payload_bytes: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), payload_bytes, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

# In your Flask/FastAPI handler:
# raw_body = await request.body()
# sig     = request.headers.get("X-BetPal-Signature", "")
# if not verify_signature(raw_body, sig, YOUR_SECRET):
#     return 400
Use a timing-safe comparison — always use hmac.compare_digest / crypto.timingSafeEqual / hash_equals rather than == to prevent timing attacks.

Retries & Timeouts

BetPal will retry a delivery up to 3 times if your endpoint returns a non-2xx status code or times out. Retries use a fixed back-off:

AttemptDelay after previous
1st (initial)
2nd5 seconds
3rd15 seconds

Your endpoint must respond within 10 seconds. If it takes longer the request is treated as failed and the next retry is scheduled.

Respond fast, process async. Return 200 OK immediately and process the payload in the background to avoid timeouts.

Request Headers

HeaderValue
Content-Typeapplication/json
X-BetPal-Signaturesha256=<hmac-hex>
X-BetPal-EventEvent name, e.g. bet.placed

Events

bet.placed A limit order was successfully submitted to Kalshi.

Fired after BetPal places an order on your behalf. The order field contains the raw Kalshi order response.

Use cases: Slack/Discord notification, trade journal entry, PnL tracking.

{
  "event": "bet.placed",
  "timestamp": "2025-03-09T18:00:00+00:00",
  "data": {
    "scenario_id":    42,
    "scenario_name":  "NHL Stanley Cup — Boston",
    "market_ticker":  "KXNHL-25-BOS-WIN",
    "market_title":   "Will the Boston Bruins win the Stanley Cup?",
    "side":           "yes",
    "quantity":       10,
    "price_cents":    45,
    "order": {
      "order_id": "ord_abc123",
      "status":   "resting"
    }
  }
}
bet.failed An order attempt was rejected by Kalshi.

Fired when the trigger condition is met but the order placement fails — e.g. insufficient funds, market closed, or API error. The scenario remains active and will retry on the next check.

Use cases: Alert to top up Kalshi balance, investigate API issues.

{
  "event": "bet.failed",
  "timestamp": "2025-03-09T18:00:05+00:00",
  "data": {
    "scenario_id":   42,
    "scenario_name": "NHL Stanley Cup — Boston",
    "market_ticker": "KXNHL-25-BOS-WIN",
    "side":          "yes",
    "quantity":      10,
    "price_cents":   45,
    "error":         "Insufficient funds",
    "http_status":   400
  }
}
scenario.error An unexpected error occurred during a bot check.

Fired when an unhandled exception is caught while processing a scenario — e.g. a network error fetching market data, or a Kalshi API outage. The scenario stays active.

Use cases: On-call alert, error monitoring integration.

{
  "event": "scenario.error",
  "timestamp": "2025-03-09T18:00:10+00:00",
  "data": {
    "scenario_id":   42,
    "scenario_name": "NHL Stanley Cup — Boston",
    "market_ticker": "KXNHL-25-BOS-WIN",
    "error":         "Connection timeout fetching market data"
  }
}
balance.low Your Kalshi balance dropped below the configured threshold.

Checked once per hour. Fires when your available Kalshi balance is less than the threshold you set in Webhook Settings (default $10). This prevents silent bet failures due to insufficient funds.

Use cases: Auto-top-up trigger, low-balance SMS alert.

{
  "event": "balance.low",
  "timestamp": "2025-03-09T19:00:00+00:00",
  "data": {
    "balance":   4.25,
    "threshold": 10.00,
    "currency":  "USD"
  }
}
trigger.approaching Market price is within 5¢ of your trigger price.

Fired once per scenario when the current ask price comes within 5¢ of your configured trigger. Useful for a heads-up before the bot fires so you can decide whether to adjust your trigger or cancel the scenario manually.

Use cases: Pre-trade notification, manual review before order fires.

{
  "event": "trigger.approaching",
  "timestamp": "2025-03-09T19:55:00+00:00",
  "data": {
    "scenario_id":   42,
    "scenario_name": "NHL Stanley Cup — Boston",
    "market_ticker": "KXNHL-25-BOS-WIN",
    "side":          "yes",
    "current_price": 48,
    "trigger_price": 45,
    "gap_cents":     3
  }
}

Best Practices

  • Respond immediately. Return 200 OK as soon as you receive the request and process asynchronously. Slow responses tie up the retry queue.
  • Verify every signature. Reject requests that fail HMAC verification — never process unverified payloads.
  • Be idempotent. Network issues can cause duplicate deliveries. Use scenario_id + event + timestamp to deduplicate if needed.
  • Use HTTPS. HTTP endpoints are accepted but your signing secret and payload will be transmitted unencrypted.
  • Rotate secrets periodically. You can regenerate your secret at any time from the Webhooks page — update your endpoint before regenerating to avoid downtime.
  • Monitor the delivery log. The Webhooks page shows the last 50 deliveries with status codes and raw response bodies so you can debug failures quickly.