Skip to content

Commit b8284ff

Browse files
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 8745189 commit b8284ff

File tree

8 files changed

+764
-2
lines changed

8 files changed

+764
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,6 @@ dmypy.json
144144
# OS
145145
.DS_Store
146146
Thumbs.db
147+
148+
# Other
149+
yoni/

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
13+
14+
### Changed
15+
16+
- HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
17+
818
## [0.2.0] - 2025-11-16
919

10-
- Entirely refactored SDK
20+
- Entirely refactored SDK

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
@@ -13,6 +13,7 @@ Complete reference for all Tango Python SDK methods and functionality.
1313
- [Notices](#notices)
1414
- [Grants](#grants)
1515
- [Business Types](#business-types)
16+
- [Webhooks](#webhooks)
1617
- [Response Objects](#response-objects)
1718
- [Error Handling](#error-handling)
1819

@@ -606,6 +607,135 @@ for biz_type in business_types.results:
606607

607608
---
608609

610+
## Webhooks
611+
612+
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
613+
614+
### list_webhook_event_types()
615+
616+
Discover supported `event_type` values and subject types.
617+
618+
```python
619+
info = client.list_webhook_event_types()
620+
print(info.event_types[0].event_type)
621+
```
622+
623+
### list_webhook_subscriptions()
624+
625+
```python
626+
subs = client.list_webhook_subscriptions(page=1, page_size=25)
627+
```
628+
629+
Notes:
630+
631+
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
632+
633+
### create_webhook_subscription()
634+
635+
```python
636+
sub = client.create_webhook_subscription(
637+
"Track specific vendors",
638+
{
639+
"records": [
640+
{"event_type": "awards.new_award", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
641+
{"event_type": "awards.new_transaction", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
642+
]
643+
},
644+
)
645+
```
646+
647+
Notes:
648+
649+
- Prefer v2 fields: `subject_type` + `subject_ids`.
650+
- Legacy compatibility: `resource_ids` is accepted as an alias for `subject_ids` (don’t send both).
651+
- Catch-all: `subject_ids: []` means “all subjects” for that record and is **Enterprise-only**. Large tier users must list specific IDs.
652+
653+
### update_webhook_subscription()
654+
655+
```python
656+
sub = client.update_webhook_subscription("SUBSCRIPTION_UUID", subscription_name="Updated name")
657+
```
658+
659+
### delete_webhook_subscription()
660+
661+
```python
662+
client.delete_webhook_subscription("SUBSCRIPTION_UUID")
663+
```
664+
665+
### list_webhook_endpoints()
666+
667+
List your webhook endpoint(s).
668+
669+
```python
670+
endpoints = client.list_webhook_endpoints(page=1, limit=25)
671+
```
672+
673+
### get_webhook_endpoint()
674+
675+
```python
676+
endpoint = client.get_webhook_endpoint("ENDPOINT_UUID")
677+
```
678+
679+
### create_webhook_endpoint() / update_webhook_endpoint() / delete_webhook_endpoint()
680+
681+
In production, MakeGov provisions the initial endpoint for you. These are most useful for dev/self-service.
682+
683+
```python
684+
endpoint = client.create_webhook_endpoint("https://example.com/tango/webhooks")
685+
endpoint = client.update_webhook_endpoint(endpoint.id, is_active=False)
686+
client.delete_webhook_endpoint(endpoint.id)
687+
```
688+
689+
### test_webhook_delivery()
690+
691+
Send an immediate test webhook to your configured endpoint.
692+
693+
```python
694+
result = client.test_webhook_delivery()
695+
print(result.success, result.status_code)
696+
```
697+
698+
### get_webhook_sample_payload()
699+
700+
Fetch Tango-shaped sample deliveries (and sample subscription request bodies).
701+
702+
```python
703+
sample = client.get_webhook_sample_payload(event_type="awards.new_award")
704+
print(sample["event_type"])
705+
```
706+
707+
### Deliveries / redelivery
708+
709+
The API does not currently expose a public `/api/webhooks/deliveries/` or redelivery endpoint. Use:
710+
711+
- `test_webhook_delivery()` for connectivity checks
712+
- `get_webhook_sample_payload()` for building handlers + subscription payloads
713+
714+
### Receiving webhooks (signature verification)
715+
716+
Every delivery includes an HMAC signature header:
717+
718+
- `X-Tango-Signature: sha256=<hex digest>`
719+
720+
Compute the digest over the **raw request body bytes** using your shared secret.
721+
722+
```python
723+
import hashlib
724+
import hmac
725+
726+
727+
def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
728+
if not signature_header:
729+
return False
730+
sig = signature_header.strip()
731+
if sig.startswith("sha256="):
732+
sig = sig[len("sha256=") :]
733+
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
734+
return hmac.compare_digest(expected, sig)
735+
```
736+
737+
---
738+
609739
## Response Objects
610740

611741
### 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)