hookrail-python
Python client for Hookrail — durable, at-least-once webhook delivery. Covers the public producer surface: send events, check delivery status, and verify incoming webhook signatures.
Install
pip install hookrail
# or
uv add hookrail
Requires Python 3.10–3.13.
Quickstart (sync)
from hookrail import Hookrail
with Hookrail(api_key="hk_...", base_url="https://hooks.example.com") as client:
accepted = client.send_event("orders.created", {"order_id": "o_1", "amount": 4200})
print("event:", accepted.event_id, "replayed:", accepted.replayed)
status = client.get_event(accepted.event_id)
for d in status.deliveries:
print(d.delivery_id, d.state)
Quickstart (async)
import asyncio
from hookrail import AsyncHookrail
async def main() -> None:
async with AsyncHookrail(api_key="hk_...", base_url="https://hooks.example.com") as client:
accepted = await client.send_event("orders.created", {"order_id": "o_1"})
print(accepted.event_id)
asyncio.run(main())
Error model
Every SDK error derives from HookrailError.
| Exception | HTTP status | Meaning |
|---|---|---|
HookrailConfigError |
— | Invalid client construction (missing key, bad URL, etc.) |
HookrailConnectionError |
— | Connect / DNS / TLS failure |
HookrailTimeoutError |
— | Connect / read / write timeout |
BadRequestError |
400 | Malformed request body or topic |
AuthenticationError |
401 | Missing or invalid hk_… producer key |
NotFoundError |
404 | Event not found (also masks certain server outages — see caveats) |
ConflictError |
409 | Idempotency key collision with different payload bytes |
PayloadTooLargeError |
413 | Event payload exceeds server limit |
RateLimitError |
429 | Rate limited; carries retry_after (seconds) |
ServerError |
5xx | Server-side error |
RetryError |
— | All retries exhausted; wraps the last error as __cause__ |
HTTP 4xx errors that are not retried: 400, 401, 404, 409, 413.
Retry and idempotency
- Automatic retries on transport errors, 408, 425, 429, and all 5xx statuses.
- Full-jitter capped exponential backoff with configurable
RetryPolicy. TheRetry-Afterheader is honoured as a floor (jitter applied on top). - Byte-identical retries: if the server has already processed the event
(same idempotency key + identical raw bytes), you receive a
202withreplayed=True— no error. If the bytes differ, a409 Conflictis returned. - Idempotency keys are automatically generated per request. You do not need to supply one.
Verifying webhook signatures
from hookrail import verify_signature
from hookrail.signing import HEADER, SignatureError
def handle(headers: dict[str, str], delivery_id: str, raw_body: bytes, secret: bytes) -> bool:
try:
verify_signature(secret, headers[HEADER], delivery_id, raw_body)
except SignatureError:
return False
return True
The verify_signature helper supports dual-secret rotation (pass a list of
bytes), configurable timestamp tolerance (default 300 s), and raises typed
exceptions (SignatureError, SignatureTimestampError,
MalformedSignatureError).
Caveats / residual risk
get_eventreturning 404 can mask a transient server outage (known server bug). Production code should treat a 404 fromget_eventas ambiguous — the event may still have been accepted.- Per-worker rate limits apply at the server; the client respects
Retry-Afterbut does not coordinate across processes.
License
Apache-2.0 — see LICENSE.
Metadata
Release files for hookrail 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hookrail-0.1.0.tar.gz | 90.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hookrail-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 107.6 kB
Release files / hookrail-0.1.0.tar.gz
| Download URL | hookrail-0.1.0.tar.gz |
|---|---|
| Size | 90.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
93b133f0ade0ddb241e56f5e666bc32078fe1e317133e89ed63d77fb3ed66c56
|
|
BLAKE2b-256 checksum How to use checksums |
a41daa97db3d1833b4cfdf2882eb9aed14e2f2a4ef3e095a7ac95a90aff6a874
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency logRelease files / hookrail-0.1.0-py3-none-any.whl
| Download URL | hookrail-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6bd6cd01f67238b6e0142714ac0d4c1275d411ddbcb58768235298190c5ea97c
|
|
BLAKE2b-256 checksum How to use checksums |
bb63b75fc764b940b086500daecb7d7dad845424de3c602ce39fe8adf8cda219
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency log