Metadata-Version: 2.4
Name: upsec-webhook
Version: 2.0.0
Summary: UpSec Webhook signature verification SDK for Python
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Security
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: requires-python
Dynamic: summary

# upsec-webhook

Official Python SDK for UpSec — webhook signature verification & API client.

## Installation

```bash
pip install upsec-webhook
```

## Features

- ✅ **Signature Verification** — HMAC constant-time comparison (SHA-256, SHA-512, etc.)
- ✅ **Flask Decorator** — Auto-reject invalid signatures with 401
- ✅ **API Client** — Full typed access to UpSec endpoints, events, destinations & delivery attempts
- ✅ **Zero Dependencies** — Uses only Python standard library

---

## Quick Start — Signature Verification

```python
from upsec import verify_signature

is_valid = verify_signature(
    payload=request.get_data(),
    secret=os.environ["WEBHOOK_SECRET"],
    signature=request.headers.get("X-Hub-Signature-256", ""),
)
```

## Flask Decorator

```python
from upsec.middleware import flask_webhook

@app.route("/webhook", methods=["POST"])
@flask_webhook(secret=os.environ["WEBHOOK_SECRET"])
def handle_webhook():
    # Signature verified automatically
    return {"ok": True}
```

---

## API Client

The SDK includes a full-featured API client to interact with your UpSec dashboard programmatically.

### Initialize

```python
from upsec import UpSec

client = UpSec(api_key="sk_live_...")
```

### List Endpoints

```python
endpoints = client.endpoints.list()
for ep in endpoints:
    print(f"{ep.name} — {ep.target_url} (active: {ep.active})")
```

### List Events

```python
result = client.events.list(endpoint_id="ep_abc123", limit=20)
print(f"{result['total']} total events")

for event in result["data"]:
    print(f"{event.source} — valid: {event.signature_valid}")
```

### List Destinations

```python
destinations = client.destinations.list()
for dest in destinations:
    print(f"{dest.name} → {dest.target_url}")
```

### List Delivery Attempts

```python
attempts = client.delivery_attempts.list(event_id="evt_abc123", limit=50)
for a in attempts:
    print(f"Attempt #{a.attempt_number}: {a.status_code}")
    if a.error_message:
        print(f"  Error: {a.error_message}")
```

### Get Profile

```python
me = client.me()
print(f"{me.full_name} ({me.subscription_status})")
```

### Error Handling

```python
from upsec import UpSec, UpSecApiError

try:
    endpoints = client.endpoints.list()
except UpSecApiError as e:
    print(f"API Error {e.status}: {e}")
```

---

## Full Example: Verify + Log

```python
import os
from flask import Flask, request
from upsec import UpSec, verify_signature
from upsec.middleware import flask_webhook

app = Flask(__name__)
client = UpSec(api_key=os.environ["UPSEC_API_KEY"])

@app.route("/webhook", methods=["POST"])
@flask_webhook(secret=os.environ["WEBHOOK_SECRET"])
def handle_webhook():
    # Check recent events via API
    result = client.events.list(limit=5)
    print(f"Recent events: {len(result['data'])}")
    return {"ok": True}
```

---

## API Reference

### Signature Verification

#### `verify_signature(payload, secret, signature, algorithm="sha256")`

Returns `True` if the HMAC signature is valid. Uses constant-time comparison.

#### `sign_payload(payload, secret, algorithm="sha256")`

Returns a signed header string like `sha256=<hex>`. Useful for testing.

#### `flask_webhook(secret, header, algorithm)`

Flask decorator that auto-rejects invalid signatures with 401.

### API Client

#### `UpSec(api_key, base_url=None)`

| Param      | Type  | Description                        |
|------------|-------|------------------------------------|
| `api_key`  | `str` | Your API key (`sk_live_...`)       |
| `base_url` | `str` | Custom API base URL (optional)     |

#### Resources

| Method                                       | Returns                    |
|----------------------------------------------|----------------------------|
| `client.endpoints.list()`                    | `List[UpSecEndpoint]`      |
| `client.events.list(endpoint_id?, limit?, offset?)` | `{ data, total }`   |
| `client.destinations.list()`                 | `List[UpSecDestination]`   |
| `client.delivery_attempts.list(event_id?, limit?)` | `List[UpSecDeliveryAttempt]` |
| `client.me()`                                | `UpSecProfile`             |

### Data Classes

All API responses return typed dataclass objects:

- `UpSecEndpoint` — id, name, target_url, active, created_at, workspace_id
- `UpSecEvent` — id, endpoint_id, source, status_code, signature_valid, created_at, payload_json
- `UpSecDestination` — id, name, target_url, active, endpoint_id, transform_rules, created_at
- `UpSecDeliveryAttempt` — id, event_id, destination_id, status_code, attempt_number, error_message, response_body, next_retry_at, created_at
- `UpSecProfile` — id, full_name, company_name, subscription_status, created_at

## License

MIT
