Metadata-Version: 2.5
Name: streamgine
Version: 1.0.1
Summary: Real-time webhook events for California corporate registry changes — Diff by Streamgine Python SDK.
Project-URL: Homepage, https://diff.streamgine.com
Project-URL: Documentation, https://diff.streamgine.com/docs.md
Project-URL: Repository, https://github.com/scurtutech/diff-events-service
Project-URL: Issues, https://github.com/scurtutech/diff-events-service/issues
Project-URL: Changelog, https://github.com/scurtutech/diff-events-service/releases
Author-email: Streamgine <hello@streamgine.com>
License: MIT
Keywords: agents,compliance,corporate-standing,diff,streamgine,webhooks
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# streamgine — Python SDK

Real-time webhook events for California corporate registry changes. Get notified the moment a company is added, updated, or removed — as structured JSON diffs, delivered to your endpoint.

**Product:** [diff.streamgine.com](https://diff.streamgine.com) · **Docs:** [docs.md](https://diff.streamgine.com/docs.md) · **Coverage:** [California](https://diff.streamgine.com/california) · **Use cases:** [risk monitoring](https://diff.streamgine.com/use-cases/risk-monitoring), [AI agents](https://diff.streamgine.com/use-cases/ai-agents)

## Install

```bash
pip install streamgine
```

Requires Python 3.10+. One dependency: [httpx](https://www.python-httpx.org/).

## Quickstart

Get your credentials from [diff.streamgine.com/account](https://diff.streamgine.com/account), then:

```python
from streamgine import diff

diff.configure("diff-streamgine-credentials.json")
# or: source diff-streamgine-env.sh   then   diff.configure()

client = diff.DiffClient()
```

### 1. Confirm your webhook is reachable

Before wiring real events, send yourself a test heartbeat:

```python
client.register_test(callback_url="https://your-agent.example/webhooks/diff")
```

You'll start receiving a signed test event every second on topic `diff-event.test`:

```json
{ "action": "INSERT", "after": { "test": "success" } }
```

Filter on `event.is_test_heartbeat` to ignore these once you're live.

### 2. Subscribe to real events

```python
client.register(
    match={"state": "CA", "principal_city": "San Francisco"},
    actions=["INSERT"],
    callback_url="https://your-agent.example/webhooks/diff",
)
```

`INSERT` = new company, `UPDATE` = changed, `DELETE` = removed. Omit `actions` to receive all three.

### 3. Search current company records

Webhooks deliver changes over time. `search()` returns current snapshots:

```python
result = client.search(query={"match": {"principalCity": "San Francisco"}}, size=10)
for company in result["entities"]:
    print(company.get("entity_name"), company.get("entity_number"))
```

### 4. Verify inbound webhooks (required)

Always verify raw request bytes before trusting the payload:

```python
def handle_webhook(raw_body: bytes, signature: str | None):
    try:
        event = diff.verify_webhook(raw_body, signature)
    except diff.WebhookVerificationError:
        return 401, {"error": "invalid signature"}

    print(event.action, event.entity_id, event.after)
    return 200, {"ok": True}
```

**FastAPI**

```python
from fastapi import FastAPI, Header, Request, Response
from streamgine import diff

app = FastAPI()

@app.on_event("startup")
def setup():
    diff.configure()

@app.post("/webhooks/diff")
async def diff_webhook(
    request: Request,
    response: Response,
    x_diff_signature: str | None = Header(default=None, alias="X-Diff-Signature"),
):
    raw = await request.body()
    try:
        event = diff.verify_webhook(raw, x_diff_signature)
    except diff.WebhookVerificationError:
        response.status_code = 401
        return {"error": "invalid signature"}
    return {"ok": True, "entity_id": event.entity_id}
```

**Flask**

```python
from flask import Flask, request
from streamgine import diff

app = Flask(__name__)
diff.configure()

@app.post("/webhooks/diff")
def webhook():
    try:
        event = diff.verify_webhook(
            request.get_data(),
            request.headers.get("X-Diff-Signature"),
        )
    except diff.WebhookVerificationError:
        return {"error": "invalid signature"}, 401
    return {"ok": True, "action": event.action}
```

## Security

| Practice                    | How this client helps                                                                                 |
| --------------------------- | ----------------------------------------------------------------------------------------------------- |
| Verify every webhook        | `verify_webhook()` uses timing-safe comparison on the raw body                                        |
| Match the signing algorithm | `HMAC-SHA256(secret, raw_json_bytes)` → header `X-Diff-Signature: sha256=<hex>`                       |
| Parse only after verifying  | Tampered bodies fail before an event object is built                                                  |
| HTTPS by default            | TLS verification is on unless explicitly disabled                                                     |
| Keep secrets in one place   | One `configure()` call from a JSON file or env vars — never pass keys directly to client constructors |

Never log your signing secret, and never skip verification in production.

## Credentials

Download from [diff.streamgine.com/account](https://diff.streamgine.com/account):

| File                               | Then call                                               |
| ---------------------------------- | ------------------------------------------------------- |
| `diff-streamgine-credentials.json` | `diff.configure("diff-streamgine-credentials.json")`    |
| `diff-streamgine-env.sh`           | `source diff-streamgine-env.sh` then `diff.configure()` |

Call `configure()` once at startup — every `DiffClient()` and `verify_webhook()` call reads from it.

## API surface (`streamgine.diff`)

| Symbol                     | Role                                                                   |
| -------------------------- | ---------------------------------------------------------------------- |
| `configure()`              | Load credentials once (file path or environment)                       |
| `get_config()`             | Read the active configuration                                          |
| `DiffClient`               | `register()`, `register_test()`, `health()`, `get_state()`, `search()` |
| `verify_webhook()`         | Verify a webhook signature and return a parsed event                   |
| `WebhookEvent`             | Parsed `action`, `entity_id`, and `after` fields                       |
| `WebhookVerificationError` | Raised on invalid or missing signatures                                |

## Links

- [Documentation](https://diff.streamgine.com/docs.md)
- [Homepage](https://streamgine.com)
- [Issues](https://github.com/scurtutech/diff-events-service/issues)
- [Changelog](https://github.com/scurtutech/diff-events-service/releases)
