Skip to content

Commit f49271b

Browse files
infinityplusonevdavez
authored andcommitted
Implement Webhooks v2 support with CRUD operations for subscriptions and endpoints, including event type discovery and test delivery functionality. Update documentation and README to reflect new features.
1 parent 2ad8a28 commit f49271b

File tree

8 files changed

+754
-3
lines changed

8 files changed

+754
-3
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,6 @@ dmypy.json
145145
.DS_Store
146146
Thumbs.db
147147

148-
# other
149-
/yoni
148+
# Other
149+
yoni/
150150
.cursor/*

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
1212
- IDV endpoints: `list_idvs`, `get_idv`, `list_idv_awards`, `list_idv_child_idvs`, `list_idv_transactions`, `get_idv_summary`, `list_idv_summary_awards`. (refs `makegov/tango#1328`)
13+
- Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
1314

1415
### Changed
1516
- Expanded explicit schemas to support common IDV shaping expansions (award offices, officers, period of performance, etc.).
17+
- HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
1618

1719
## [0.2.0] - 2025-11-16
1820

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A modern Python SDK for the [Tango API](https://tango.makegov.com) by MakeGov, f
66

77
- **Dynamic Response Shaping** - Request only the fields you need, reducing payload sizes by 60-80%
88
- **Full Type Safety** - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
9-
- **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
9+
- **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants, webhooks) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
1010
- **Flexible Data Access** - Dictionary-based response objects with validation
1111
- **Modern Python** - Built for Python 3.12+ using modern async-ready patterns
1212
- **Production-Ready** - Comprehensive test suite with VCR.py-based integration tests

docs/API_REFERENCE.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Complete reference for all Tango Python SDK methods and functionality.
1515
- [Notices](#notices)
1616
- [Grants](#grants)
1717
- [Business Types](#business-types)
18+
- [Webhooks](#webhooks)
1819
- [Response Objects](#response-objects)
1920
- [Error Handling](#error-handling)
2021

@@ -721,6 +722,135 @@ for biz_type in business_types.results:
721722

722723
---
723724

725+
## Webhooks
726+
727+
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
728+
729+
### list_webhook_event_types()
730+
731+
Discover supported `event_type` values and subject types.
732+
733+
```python
734+
info = client.list_webhook_event_types()
735+
print(info.event_types[0].event_type)
736+
```
737+
738+
### list_webhook_subscriptions()
739+
740+
```python
741+
subs = client.list_webhook_subscriptions(page=1, page_size=25)
742+
```
743+
744+
Notes:
745+
746+
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
747+
748+
### create_webhook_subscription()
749+
750+
```python
751+
sub = client.create_webhook_subscription(
752+
"Track specific vendors",
753+
{
754+
"records": [
755+
{"event_type": "awards.new_award", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
756+
{"event_type": "awards.new_transaction", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
757+
]
758+
},
759+
)
760+
```
761+
762+
Notes:
763+
764+
- Prefer v2 fields: `subject_type` + `subject_ids`.
765+
- Legacy compatibility: `resource_ids` is accepted as an alias for `subject_ids` (don’t send both).
766+
- Catch-all: `subject_ids: []` means “all subjects” for that record and is **Enterprise-only**. Large tier users must list specific IDs.
767+
768+
### update_webhook_subscription()
769+
770+
```python
771+
sub = client.update_webhook_subscription("SUBSCRIPTION_UUID", subscription_name="Updated name")
772+
```
773+
774+
### delete_webhook_subscription()
775+
776+
```python
777+
client.delete_webhook_subscription("SUBSCRIPTION_UUID")
778+
```
779+
780+
### list_webhook_endpoints()
781+
782+
List your webhook endpoint(s).
783+
784+
```python
785+
endpoints = client.list_webhook_endpoints(page=1, limit=25)
786+
```
787+
788+
### get_webhook_endpoint()
789+
790+
```python
791+
endpoint = client.get_webhook_endpoint("ENDPOINT_UUID")
792+
```
793+
794+
### create_webhook_endpoint() / update_webhook_endpoint() / delete_webhook_endpoint()
795+
796+
In production, MakeGov provisions the initial endpoint for you. These are most useful for dev/self-service.
797+
798+
```python
799+
endpoint = client.create_webhook_endpoint("https://example.com/tango/webhooks")
800+
endpoint = client.update_webhook_endpoint(endpoint.id, is_active=False)
801+
client.delete_webhook_endpoint(endpoint.id)
802+
```
803+
804+
### test_webhook_delivery()
805+
806+
Send an immediate test webhook to your configured endpoint.
807+
808+
```python
809+
result = client.test_webhook_delivery()
810+
print(result.success, result.status_code)
811+
```
812+
813+
### get_webhook_sample_payload()
814+
815+
Fetch Tango-shaped sample deliveries (and sample subscription request bodies).
816+
817+
```python
818+
sample = client.get_webhook_sample_payload(event_type="awards.new_award")
819+
print(sample["event_type"])
820+
```
821+
822+
### Deliveries / redelivery
823+
824+
The API does not currently expose a public `/api/webhooks/deliveries/` or redelivery endpoint. Use:
825+
826+
- `test_webhook_delivery()` for connectivity checks
827+
- `get_webhook_sample_payload()` for building handlers + subscription payloads
828+
829+
### Receiving webhooks (signature verification)
830+
831+
Every delivery includes an HMAC signature header:
832+
833+
- `X-Tango-Signature: sha256=<hex digest>`
834+
835+
Compute the digest over the **raw request body bytes** using your shared secret.
836+
837+
```python
838+
import hashlib
839+
import hmac
840+
841+
842+
def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
843+
if not signature_header:
844+
return False
845+
sig = signature_header.strip()
846+
if sig.startswith("sha256="):
847+
sig = sig[len("sha256=") :]
848+
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
849+
return hmac.compare_digest(expected, sig)
850+
```
851+
852+
---
853+
724854
## Response Objects
725855

726856
### PaginatedResponse

tango/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
PaginatedResponse,
1313
SearchFilters,
1414
ShapeConfig,
15+
WebhookEndpoint,
16+
WebhookEventType,
17+
WebhookEventTypesResponse,
18+
WebhookSubjectTypeDefinition,
19+
WebhookSubscription,
20+
WebhookTestDeliveryResult,
1521
)
1622
from .shapes import (
1723
ModelFactory,
@@ -31,6 +37,12 @@
3137
"PaginatedResponse",
3238
"SearchFilters",
3339
"ShapeConfig",
40+
"WebhookEndpoint",
41+
"WebhookEventType",
42+
"WebhookEventTypesResponse",
43+
"WebhookSubscription",
44+
"WebhookSubjectTypeDefinition",
45+
"WebhookTestDeliveryResult",
3446
"ShapeParser",
3547
"ModelFactory",
3648
"TypeGenerator",

0 commit comments

Comments
 (0)