Metadata-Version: 2.4
Name: hooka-relay-python
Version: 1.0.0
Summary: Typed Hooka Relay event client and Standard Webhooks verification
License-Expression: MIT
Project-URL: Documentation, https://hooka-relay.vercel.app/docs
Project-URL: Source, https://github.com/wauul/hooka-relay/tree/master/packages/python
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: standardwebhooks==1.1.0
Dynamic: license-file

# hooka-relay-python

Python 3.11+ client for [Hooka Relay](https://hooka-relay.vercel.app/docs#api-reference).

```sh
pip install hooka-relay-python
```

```python
import os
from hooka_relay import HookaRelay
relay = HookaRelay(os.environ["HOOKA_API_KEY"])
event = relay.send_event({
    "type": "order.created", "payload": {"orderId": "123"},
    "idempotencyKey": "order-123-created",
})
print(event["id"])
```

Use an ingest-only or existing unscoped application key. Optional `base_url` and `timeout` (seconds, default 30) configure the client. Remote URLs require HTTPS; redirects are rejected to avoid credential forwarding. There are no implicit retries. Retry ambiguous network failures with the **same explicit idempotencyKey**; omitted keys are generated by the server, so resending without one can create another event.

`HookaError` exposes `status`, parsed `body`, and `retry_after`. Rate limits return 429, oversized requests 413, and schema failures 400 with `failures: [{path, message}]`. Payload limits: 256 KiB / JSON depth 32. TypedDict models are generated from the repository's OpenAPI contract; runtime server validation remains authoritative.

## Verify and queue, then drain

`verify_webhook(raw_body, headers, secret)` uses the Standard Webhooks reference library. It raises on invalid signatures, a changed ID/body, or timestamps outside five minutes. Pass raw bytes and the displayed `whsec_` secret. During rotation either old or new key verifies the dual signatures. Existing LEGACY endpoints must explicitly migrate after their receiver supports Standard Webhooks.

Minimal queue-and-drain receiver using the standard library (put a production HTTP server/reverse proxy in front of a real deployment). A bounded queue rejects overload with 503; accepted work is processed outside the request.

```python
import os
import logging
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from queue import Queue, Full
from threading import Thread
from hooka_relay import verify_webhook

queue = Queue(maxsize=1000)

def process_event(item):
    print(item["id"], item["payload"])

def drain():
    while True:
        item = queue.get()
        try:
            process_event(item)
        except Exception:
            logging.exception("Persist failed item to your dead-letter store: %s", item["id"])
        finally:
            queue.task_done()

class Receiver(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != "/webhook":
            self.send_error(404)
            return
        try:
            length = int(self.headers.get("Content-Length", "-1"))
        except ValueError:
            self.send_error(400)
            return
        if length < 0 or self.headers.get("Transfer-Encoding"):
            self.send_error(411)
            return
        if length > 262144:
            self.send_error(413)
            return
        try:
            headers = {k: self.headers.get(k, "") for k in ("webhook-id", "webhook-timestamp", "webhook-signature")}
            payload = verify_webhook(self.rfile.read(length), headers, os.environ["HOOKA_SIGNING_SECRET"])
        except Exception:
            self.send_error(400)
            return
        try:
            queue.put_nowait({"id": headers["webhook-id"], "payload": payload})
        except Full:
            self.send_error(503)
            return
        self.send_response(202)
        self.end_headers()

Thread(target=drain, daemon=True).start()
ThreadingHTTPServer(("127.0.0.1", 8080), Receiver).serve_forever()
```

The queue is **volatile**: a crash loses already acknowledged work. For production, persist to a durable inbox/queue before acknowledging. Atomically deduplicate the authenticated `webhook-id` with business changes. Persist processing failures for retry or dead-letter handling; a log entry is not durable recovery.

## Event ordering

Ordering is **not guaranteed across retries or replay generations**. The queue-and-drain example above separates acknowledgement from slow processing and isolates failures. It cannot restore producer order; apply per-entity sequence/version checks if required.

[Interactive API reference](https://hooka-relay.vercel.app/docs#api-reference) · [Security and migration](https://github.com/wauul/hooka-relay/blob/master/SECURITY.md)
