Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project are documented in this file. The format foll

### Added

- Accept webhook contract `2026-10-06` with signed event IDs, final result/scoring versions, the complete risk-event catalogue, three HRE results and distinct fp21 hardware identity. Legacy webhook bodies remain supported.

- `sync.sh` downloads the OpenAPI description and `generate.sh` rebuilds `generated/` from it. The supported client is unchanged.

## [1.0.0] - 2026-09-30
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,10 @@ def handle_delivery(raw_body: bytes, signature: Optional[str]) -> int:
- Verify the raw bytes exactly as received, before parsing. Re-serialized JSON does not match.
- `secret` can be a list: a delivery is valid when any secret matches, so you can rotate an
endpoint secret without downtime.
- ShieldLabs sends one delivery per identification and endpoint, with a 1-second timeout and no
retries. Respond with a 2xx within 1 second and do slow work afterwards.
- Make handlers idempotent on `data.request_id`: a future release retries deliveries, and a
- Each endpoint has independent delivery state, a 1-second send timeout and bounded retries. Persist before 2xx and process asynchronously.
- Make handlers idempotent on `data.request_id`: current retries reuse event_id, and a
retry resends identical bytes.
- Use the History API for guaranteed reads and for the latest state: a delivery that fails is
not sent again, and a History row can be refined after its webhook was sent.
- Use the History API for guaranteed reads and for the latest state: retry exhaustion requires recovery, and a History row can be refined after its webhook was sent.
- `construct_event` returns `IdentificationScoredEvent`, `WebhookPingEvent` or
`UnknownWebhookEvent`, and never raises for an unknown event type. The **Test** delivery sent
from the analytics dashboard parses like production traffic.
Expand Down Expand Up @@ -470,3 +468,19 @@ error responses) that every ShieldLabs server SDK passes. See
## License

[MIT](LICENSE)


### Webhook contract 2026-10-06

Current events include signed `event_id`, optional `site_id`, the complete `data.risk_events`
catalogue (including zero-weight events), `data.fingerprint` (FP21 hardware ID distinct from
`device_id`), and `data.hre` for sharing/takeover/travel with explicit statuses. Older envelopes
remain supported. Only identification risk score is sent; AI bots/browser are planned only,
and all-time entity risks are excluded.

Persist the verified event in a durable inbox **before** returning 2xx and deduplicate by
`event_id` (legacy scored bodies: `data.request_id`). Timeout/network/429/5xx retry with
backoff in a bounded window (8 failed sends / 15-minute retry age), then DLQ. Other 4xx are
terminal. Retried bodies and event IDs stay unchanged. `X-Shield-Event-Id` mirrors the body ID;
trust the signed body. Signature verification remains raw-body HMAC-SHA256. Delivery is not
exactly-once and later History corrections do not automatically create a new webhook event.
9 changes: 5 additions & 4 deletions examples/fastapi_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,12 @@ async def shieldlabs_webhook(request: Request) -> Response:

if isinstance(event, IdentificationScoredEvent):
data = event.data
if data.request_id in processed_request_ids:
delivery_id = event.event_id or data.request_id
if delivery_id in processed_request_ids:
return Response(status_code=200) # already handled: acknowledge and stop
processed_request_ids.add(data.request_id)
# Answer fast (under 1 s): a slow or failed delivery is not sent again, so queue slow
# work instead of doing it here, and read the History API when you must not miss one.
processed_request_ids.add(delivery_id)
# Demo dedup is in memory only. Production must durably persist event_id and
# the raw body before 2xx; processing may then run asynchronously.
logger.info(
"identification.scored request_id=%s risk_score=%s band=%s flags=%s",
data.request_id,
Expand Down
12 changes: 12 additions & 0 deletions generated/.openapi-generator/FILES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions generated/shieldlabs_generated/__init__.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions generated/shieldlabs_generated/models/__init__.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

156 changes: 156 additions & 0 deletions generated/shieldlabs_generated/models/client_identity.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading