Metadata-Version: 2.4
Name: full-event-hub
Version: 1.1.8
Summary: Zero-dependency Python client for the event-hub webhook fan-out service
Author-email: Ravi Leal <ravi@example.com>
License: MIT
Project-URL: Homepage, https://github.com/ravileal/event-hub
Project-URL: Repository, https://github.com/ravileal/event-hub
Project-URL: Issues, https://github.com/ravileal/event-hub/issues
Keywords: event-hub,webhooks,hmac,fan-out,events,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
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 :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: test

# full-event-hub (Python client)

Zero-dependency Python client for [event-hub](https://github.com/ravileal/event-hub) — a
webhook fan-out service (ingest, fan-out to N subscribers, HMAC signing, retry/backoff,
attempts, retention).

Only the standard library is used (`urllib.request`, `json`, `hmac`, `hashlib`). The
package version matches the hub/image version (`ghcr.io/ravileal/event-hub`); because
they move together, the client sends the English `X-Idempotency` header that hubs
since 1.1.0 read, while a hub older than 1.1.0 ignores it.

## Install

```bash
pip install full-event-hub
```

## Publish an event

```python
from event_hub import EventHubClient

hub = EventHubClient("http://127.0.0.1:18295")

hub.create_topic("orders", description="order lifecycle")
hub.create_subscriber("orders", "billing",
                      url="http://localhost:9000/hook", secret="s3cr3t",
                      max_attempts=3, backoff_ms=200)

resp = hub.publish_event("orders", {"order_id": 1}, idempotency_key="order-1")
print(resp.status, resp.body)  # 202 {'id': 1, 'deliveries_created': 1, 'duplicado': False}
```

`publish_event` sends the idempotency key as `X-Idempotency` (the header the hub
reads since 1.1.0; it falls back to the legacy `X-Idempotencia`). Use
`sign(secret, raw_body)` to sign the exact bytes you send.

## Verify an incoming delivery

event-hub signs every delivery with HMAC-SHA256 of the **raw** body using the
subscriber secret. Pass the exact bytes received; re-serializing JSON breaks the HMAC.

```python
from event_hub import verify_delivery_signature

# in your webhook receiver:
valid = verify_delivery_signature(
    secret, raw_body, request.headers["X-Event-Hub-Signature"]
)
```

`verify_delivery_signature` returns `False` for a missing header, a wrong `sha256=`
prefix, or a non-matching digest, and compares in constant time
(`hmac.compare_digest`).

## Notes

- Idempotency header is **`X-Idempotency`** (English) since 1.1.0; the legacy
  **`X-Idempotencia`** is still accepted for old producers but new code should not
  send it.
- Deliveries carry `X-Event-Hub-Event`, `X-Event-Hub-Attempt` and
  `X-Event-Hub-Signature` (`sha256=<hex>`).

MIT licensed.
