> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplefunctions.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Register HTTPS endpoints, verify signed deliveries with HMAC-SHA256, and handle exponential-backoff retries.

SimpleFunctions delivers signed webhooks for alert events, thesis-state changes, and portfolio events. Every endpoint is HTTPS-only, signed with a shared secret, and idempotent via a stable delivery id.

## Register an endpoint

```bash theme={null}
sf webhooks create --url https://your.app/sf-hook --label ops
```

Or via API:

```http theme={null}
POST /api/webhook-endpoints
Authorization: Bearer sf_live_...
Content-Type: application/json

{ "url": "https://your.app/sf-hook", "label": "ops" }
```

Response includes the endpoint id (`we_...`) and signing secret.

## Signature verification

Every delivery includes:

```http theme={null}
X-SF-Signature: t=<unix_ts>,v1=<hex_hmac>
X-SF-Endpoint-Id: we_...
X-SF-Delivery-Id: del_...
X-SF-Event: alert.fired
```

<CodeGroup>
  ```ts TypeScript theme={null}
  import crypto from 'crypto'

  function verify(secret: string, headers: Record<string,string>, body: string): boolean {
    const sig = headers['x-sf-signature']
    if (!sig) return false
    const [tPart, vPart] = sig.split(',')
    const t = tPart.split('=')[1]
    const v1 = vPart.split('=')[1]
    const expected = crypto
      .createHmac('sha256', secret)
      .update(`${t}.${body}`)
      .digest('hex')
    if (Math.abs(Date.now()/1000 - Number(t)) > 300) return false  // 5min window
    return crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'))
  }
  ```

  ```python Python theme={null}
  import hmac, hashlib, time

  def verify(secret: str, headers: dict, body: str) -> bool:
      sig = headers.get("x-sf-signature", "")
      if not sig:
          return False
      parts = dict(p.split("=") for p in sig.split(","))
      t, v1 = parts["t"], parts["v1"]
      if abs(int(time.time()) - int(t)) > 300:
          return False
      expected = hmac.new(secret.encode(), f"{t}.{body}".encode(), hashlib.sha256).hexdigest()
      return hmac.compare_digest(v1, expected)
  ```

  ```go Go theme={null}
  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "strings"
      "time"
  )

  func verify(secret string, headers map[string]string, body string) bool {
      sig := headers["x-sf-signature"]
      parts := strings.Split(sig, ",")
      if len(parts) != 2 { return false }
      t := strings.TrimPrefix(parts[0], "t=")
      v1 := strings.TrimPrefix(parts[1], "v1=")
      ts, _ := time.Parse(time.RFC3339, t)
      if time.Since(ts).Abs() > 5*time.Minute { return false }
      h := hmac.New(sha256.New, []byte(secret))
      h.Write([]byte(t + "." + body))
      expected := hex.EncodeToString(h.Sum(nil))
      return hmac.Equal([]byte(v1), []byte(expected))
  }
  ```
</CodeGroup>

## Events

| Event                       | Payload                                                     |
| --------------------------- | ----------------------------------------------------------- |
| `alert.fired`               | Alert rule tripped; includes rule, watched object, snapshot |
| `alert.paused`              | Rule auto-paused after repeated failures                    |
| `thesis.confidence_changed` | Thesis confidence delta crossed threshold                   |
| `thesis.killed`             | Thesis kill-flag raised by monitor                          |
| `portfolio.tick_completed`  | Portfolio autopilot tick finished                           |
| `portfolio.halt_triggered`  | Drawdown halt fired                                         |

Full payload shapes: [Webhook events reference](/reference/webhook-events).

## Retries

* Delivery is attempted up to **5 times** with exponential backoff (1s, 4s, 16s, 64s, 256s).
* Endpoints that 5xx more than 50% of the time across a 24h window get **auto-paused**.
* You can re-enable with `sf webhooks resume <id>`.
* Failed deliveries are persisted in `alert_deliveries` with `status = failed` for replay.

## Test a delivery

```bash theme={null}
sf webhooks test <endpoint-id>
```

Sends a synthetic `alert.fired` event so you can verify your receiver before going live.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook events" href="/reference/webhook-events">
    Full payload schemas per event type.
  </Card>

  <Card title="Webhook receiver" href="/integrations/webhook-receiver">
    Receiver patterns with retry handling.
  </Card>

  <Card title="Watchlist + alerts" href="/build/watchlist-alerts">
    The system that produces these events.
  </Card>
</CardGroup>
