You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Vehicles: new `metrics(*)` shape expansion bundling 12 computed metrics: `avg_offers_received`, `award_concentration_hhi`, `order_concentration_hhi`, `competed_rate`, `using_agency_count`, `avg_order_value`, `max_order_value`, `top_recipient_share`, `recent_obligations_24mo`, `recent_orders_24mo`, `days_since_last_order`, `obligation_to_ceiling_ratio`. Backed by a new `VehicleMetrics` schema.
13
+
-`list_vehicle_orders(uuid, ...)` for the new `/api/vehicles/{uuid}/orders/` endpoint, returning task orders under the vehicle's IDVs with two-phase pagination.
14
+
-`list_vehicles` gained 21 explicit filter parameters per API 4.3.0: `vehicle_type`, `type_of_idc`, `contract_type`, `set_aside` (multi-value via `|`), `who_can_use`, `naics_code`, `psc_code`, `program_acronym`, `agency`, `organization_id`, `total_obligated_min`/`max`, `idv_count_min`/`max`, `order_count_min`/`max`, `fiscal_year`, `award_date_after`/`before`, `last_date_to_order_after`/`before`.
15
+
-`list_vehicle_awardees` gained a `search` parameter for entity-aware full-text search across IDV fields and recipient entity details (API 4.3.0).
16
+
-`ordering` parameter on `list_vehicles` (whitelist: `vehicle_obligations`, `latest_award_date`, `total_obligated`, `award_date`, `last_date_to_order`, `fiscal_year`, `idv_count`, `order_count`) and on `list_vehicle_orders` (whitelist: `award_date`, `obligated`, `total_contract_value`). Prefix with `-` for descending.
17
+
-`ShapeConfig.VEHICLE_ORDERS_MINIMAL` default for the new orders endpoint.
18
+
- Shaping: New `organization(*)` expand on `Vehicle`, `Forecast`, `Grant`, `ITDashboardInvestment`, and `Protest` schemas — returns the canonical 7-key office payload (`organization_id`, `office_code`, `office_name`, `agency_code`, `agency_name`, `department_code`, `department_name`). Selectable as the bare leaf (`shape=...,organization`) or as a sub-selectable expansion (`shape=...,organization(office_code,...)`).
19
+
- Shaping: New `vehicle(*)` expand on `Contract` — request the parent vehicle inline from `/api/contracts/` (API 4.2.0).
20
+
-`Vehicle` and `VehicleMetrics` are now exported from the top-level `tango` package.
21
+
-`tango.webhooks` subpackage with HMAC-SHA256 signing helpers (`verify_signature`, `generate_signature`, `parse_signature_header`) that mirror the canonical Tango server scheme byte-for-byte. Importable from a default `pip install tango-python` (pure stdlib).
22
+
-`WebhookReceiver`: a stdlib-based local HTTP listener for development and integration tests. Verifies signatures, optionally forwards each delivery to a downstream URL, and records deliveries in memory for inspection. Usable as a context manager (`with WebhookReceiver(secret=...).run() as rx: ...`).
23
+
-`tango.webhooks.simulate.deliver(...)`: locally sign and POST a payload to any URL — no Tango involvement. Useful for offline iteration on receiver code.
24
+
- New `tango[webhooks]` extra (adds `click`) ships a `tango` console script covering the full webhook lifecycle for developer integrations:
25
+
-`listen` — local receiver
26
+
-`simulate` — sign a payload locally; with `--to`, also POST it
27
+
-`trigger` — ask Tango to send a real test delivery
28
+
-`fetch-sample` — print the canonical payload Tango emits for an event type
-`subscriptions list|get|create|delete` — manage what events you receive
32
+
Together these let a developer go from zero to receiving real Tango webhooks without leaving the shell or dropping into Python.
33
+
34
+
### Changed
35
+
-`ShapeConfig.VEHICLES_MINIMAL` and `VEHICLES_COMPREHENSIVE` now include the new top-level fields and the `organization` expansion. `VEHICLES_COMPREHENSIVE` defaults to `metrics(*)` and no longer pulls the deprecated `competition_details(*)` blob.
36
+
37
+
### Deprecated
38
+
- Vehicles shape fields `agency_details`, `competition_details`, and the `opportunity` expansion. The upstream API now sends a `Deprecation: true` header for these and recomputes them at request time. Explicit use in `shape=...` emits a Python `DeprecationWarning`. Sunset timeline TBD upstream.
39
+
40
+
### Notes
41
+
- Console script name `tango` may be revisited in a future release if it conflicts with sibling tooling (`tango-scripts` reuses the bare name).
42
+
43
+
### Documentation
44
+
- New `docs/WEBHOOKS.md` — comprehensive guide covering install, concepts, a zero-to-receiving quickstart, full CLI reference, and programmatic patterns for `WebhookReceiver` / `simulate.sign` / `simulate.deliver` in pytest fixtures.
45
+
-`docs/API_REFERENCE.md`: filled in `get_webhook_subscription`, replaced the hand-rolled signature-verification snippet with a pointer to `tango.webhooks.verify_signature`, and added a new "Webhook tooling (`tango.webhooks`)" section that documents every importable from the new subpackage.
46
+
-`README.md`: new "Webhook Tooling" section under Advanced Features, plus the new guide is linked from the Documentation index.
The SDK ships first-class tooling for **building and testing webhook integrations against the Tango API** — including signing helpers, a local receiver, and a command-line tool covering the full lifecycle:
338
+
339
+
```bash
340
+
pip install 'tango-python[webhooks]'
341
+
```
342
+
343
+
This adds a `tango` console script with subcommands for the full webhook lifecycle:
The signing helpers (`verify_signature`, `generate_signature`) are pure stdlib and importable from the default install — your receiver code doesn't need the extra:
|`organization`| dict \| None | Live awarding-org snapshot `{organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name}`. Selected as a leaf field (`shape=...,organization`); not currently sub-selectable. |
593
+
|`vehicle_type`, `who_can_use`, `type_of_idc`, `contract_type`| dict \| None | Returned as `{code, description}`. |
594
+
|`description`| str \| None | Common text across IDV descriptions. |
|`award_date`, `latest_award_date`, `last_date_to_order`| date \| None ||
599
+
|`solicitation_title`, `solicitation_description`, `solicitation_date`, `opportunity_id`| str / date / None | From SAM.gov via the linked Opportunity. |
600
+
|`naics_code`, `psc_code`, `set_aside`, `fiscal_year`| int / str / None ||
-`organization` — live awarding-org snapshot (selected as a leaf field; not sub-selectable).
607
+
608
+
### Deprecated shape fields
609
+
610
+
The following fields and expansions are still served by the API (recomputed at request time from the underlying IDVs) but the API now returns a `Deprecation: true` response header for them. They will be removed in a future tango API release.
611
+
612
+
-`agency_details` (top-level field and `agency_details(*)` expansion)
613
+
-`competition_details` (top-level field and `competition_details(*)` expansion)
614
+
-`opportunity(*)` expansion (use the new top-level `solicitation_*` and `opportunity_id` fields instead)
615
+
616
+
If you pass any of these in `shape=...`, the SDK will emit a Python `DeprecationWarning`. The default shapes (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`) no longer include them.
617
+
555
618
---
556
619
557
620
## IDVs
@@ -1227,6 +1290,8 @@ for code in naics.results:
1227
1290
1228
1291
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
1229
1292
1293
+
> **For testing, signing, and a CLI tool**, see [`docs/WEBHOOKS.md`](WEBHOOKS.md). This section covers SDK method signatures only.
1294
+
1230
1295
### list_webhook_event_types()
1231
1296
1232
1297
Discover supported `event_type` values and subject types.
@@ -1246,6 +1311,12 @@ Notes:
1246
1311
1247
1312
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
1248
1313
1314
+
### get_webhook_subscription()
1315
+
1316
+
```python
1317
+
sub = client.get_webhook_subscription("SUBSCRIPTION_UUID")
1318
+
```
1319
+
1249
1320
### create_webhook_subscription()
1250
1321
1251
1322
```python
@@ -1335,21 +1406,89 @@ Every delivery includes an HMAC signature header:
1335
1406
1336
1407
Compute the digest over the **raw request body bytes** using your shared secret.
1337
1408
1409
+
The SDK ships a stdlib-only verifier that mirrors the Tango server's signing scheme byte-for-byte. Use it instead of hand-rolling — it's importable from a default install (no extras needed):
`verify_signature` returns `False` for missing/empty/malformed headers — it never raises. Comparison is constant-time.
1419
+
1420
+
---
1421
+
1422
+
## Webhook tooling (`tango.webhooks`)
1423
+
1424
+
The `tango.webhooks` subpackage adds testing and developer-tooling primitives on top of the API methods above. Signing helpers ship with the default install; the receiver and CLI ship with `pip install 'tango-python[webhooks]'`. See [`docs/WEBHOOKS.md`](WEBHOOKS.md) for usage guides; this section is the import-level reference.
A stdlib-based local HTTP receiver, useful in tests and during local development.
1441
+
1442
+
```python
1443
+
from tango.webhooks import WebhookReceiver, Delivery
1444
+
1445
+
with WebhookReceiver(secret="dev").run() as rx:
1446
+
# ... cause something to POST to rx.url ...
1447
+
deliveries: list[Delivery] = rx.deliveries
1448
+
```
1449
+
1450
+
Constructor (all keyword arguments):
1451
+
1452
+
| Arg | Default | Meaning |
1453
+
|---|---|---|
1454
+
|`secret`|`""`| Shared secret. Empty means signatures are not verified. |
1455
+
|`path`|`/tango/webhooks`| URL path to accept POSTs on. |
1456
+
|`host`|`127.0.0.1`| Bind address. |
1457
+
|`port`|`0`| TCP port. `0` = OS picks a free port. |
1458
+
|`forward_to`|`None`| Optional URL to mirror each delivery to. |
1459
+
|`max_history`|`256`| Cap on the in-memory `deliveries` deque. |
1460
+
|`on_delivery`|`None`| Callback fired for every delivery (verified or not). |
1461
+
|`require_signature`|`None`| Override default (require iff `secret` is set). |
1462
+
1463
+
Each `Delivery` is a dataclass: `received_at`, `path`, `signature_header`, `body_bytes`, `body_json`, `verified`, `remote_addr`, `forward_status`, `forward_error`.
1464
+
1465
+
### `simulate.sign` and `simulate.deliver`
1466
+
1467
+
```python
1468
+
from tango.webhooks import sign, SignedRequest
1469
+
from tango.webhooks import simulate
1470
+
1471
+
# Offline — produce the signed wire form without POSTing:
result = simulate.deliver(target_url="http://localhost:8011/tango/webhooks",
1479
+
payload={...}, secret="s")
1480
+
result.status_code # status from the receiver
1481
+
result.signature # bare hex
1482
+
result.sent_bytes # exact bytes that were POSTed
1483
+
result.response_body # body the receiver returned
1484
+
```
1485
+
1486
+
`simulate.deliver` and `simulate.sign` accept payloads as `dict`, `list`, `str`, or raw `bytes`. Dicts/lists are serialized via `json.dumps(..., sort_keys=True, separators=(",", ":"))` so signatures are reproducible across runs.
1487
+
1488
+
### CLI entry point
1489
+
1490
+
The `tango[webhooks]` extra also installs a `tango` console script. See [`docs/WEBHOOKS.md` § CLI reference](WEBHOOKS.md#cli-reference) for the full command list.
**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles, Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant.
60
+
**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`, `VEHICLE_AWARDEES_MINIMAL`, `VEHICLE_ORDERS_MINIMAL`), Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant.
61
+
62
+
> **Vehicles `metrics(*)` expansion:** The vehicles surface bundles 12 computed metrics under a single `metrics(*)` expansion (e.g. `award_concentration_hhi`, `competed_rate`, `top_recipient_share`). It is included in `VEHICLES_COMPREHENSIVE` by default. The `agency_details`, `competition_details`, and `opportunity` shape entries are deprecated and emit `DeprecationWarning` if requested explicitly.
0 commit comments