Add webhooks signing helpers, local receiver, and tango CLI#23
Closed
makegov-mark[bot] wants to merge 8 commits intomainfrom
Closed
Add webhooks signing helpers, local receiver, and tango CLI#23makegov-mark[bot] wants to merge 8 commits intomainfrom
makegov-mark[bot] wants to merge 8 commits intomainfrom
Conversation
Tier 1+2 of the Stripe-CLI-style webhook DX migration from tango/tools/ webhook_lab into the SDK. - tango.webhooks: HMAC-SHA256 signing helpers (verify_signature, generate_signature, parse_signature_header). Pure stdlib; importable from a default install. - WebhookReceiver: stdlib-based local listener with optional forwarding, delivery history, and on_delivery callback. Usable as a context manager inside integration tests. - simulate.deliver: offline sign+POST helper for driving a receiver without provisioning a real subscription. - New tango[webhooks] extra installs click and a tango console script with `webhooks listen|trigger|simulate` subcommands. - Top-level re-exports: WebhookReceiver, Delivery, verify_signature, generate_signature, parse_signature_header. The script name `tango` is flagged in CHANGELOG as revisitable before release if it conflicts with sibling tooling (e.g. tango-scripts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default BaseHTTPRequestHandler error page is HTML, which makes the
receiver's rejection responses inconsistent with tango's own JSON-shaped
API and harder to inspect programmatically (e.g. via simulate's
response_body field).
Now responds with `{"ok": false, "error": "<code>"}` and Content-Type
application/json on 401 (invalid_signature) and 404 (not_found), and
`{"ok": true}` on 200 — explicit Content-Length on all paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The simulate output's `sent_bytes` (length only) and `response_body` (the receiver's pong) were both confusing for devs who really want to see the canonical Tango shape they're driving their handler with: - `sent_bytes` (int) → `sent_payload` (the parsed JSON dict) - `response_body` → `receiver_response` (clarifies whose body it is) Two new subcommands close the discovery loop for devs building against the Tango API: - `tango webhooks fetch-sample [--event-type X]` — print the canonical sample payload Tango emits (read-only, no POST). Wraps the SDK's `get_webhook_sample_payload`. - `tango webhooks list-event-types` — list every event type Tango supports with descriptions. Wraps `list_webhook_event_types`. Together: `list-event-types` → pick one → `fetch-sample` to see the shape → `simulate --event-type X` to drive the handler with that shape, all without leaving the shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A dev exploring "what would Tango send my handler" shouldn't have to stand up a listener first. Now: - `simulate --event-type X` (no --to) signs the canonical payload and prints the headers + body — no POST. Output includes the literal X-Tango-Signature value the handler would see. - `simulate --event-type X --to URL` keeps the previous behavior: signs, POSTs, prints the receiver's response. Output gained `delivered: bool` so callers can disambiguate the two modes from the JSON itself. Refactored simulate to expose a public `simulate.sign(payload, secret) -> SignedRequest`. Useful in pytest fixtures that want the wire bytes without spinning a process. `simulate.deliver` now goes through it. Top-level re-exports: `from tango.webhooks import sign, SignedRequest`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the dev-against-Tango workflow loop — a dev with TANGO_API_KEY can now go from zero to receiving real deliveries entirely from the shell. Previously they had to drop into Python to call create_webhook_endpoint / create_webhook_subscription. Added: - `tango webhooks endpoints list|get|create|delete` - `tango webhooks subscriptions list|get|create|delete` Notes: - `subscriptions create` accepts simple flags (--name, --event-type, --subject-type, --subject-id) and assembles them into the payload.records[] shape Tango expects. For multi-record subscriptions, use the SDK's create_webhook_subscription directly. - `delete` requires confirmation; pass --yes to skip. - update commands deferred — endpoints/subscriptions are usually delete-and-recreate during dev, and partial-update semantics are awkward to express via flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`test_cli_simulate_rejects_both_modes` was passing /dev/null as a placeholder file. Click validates --payload-file with exists=True before the command body runs, so on Windows CI the test bailed with "File '/dev/null' does not exist" before ever reaching the mutual- exclusion error it was checking for. Use tmp_path to get a real existing file. macOS/Linux already passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New `docs/WEBHOOKS.md` (~370 lines) — single comprehensive guide for developers building against the Tango API: install, concepts, a zero-to-receiving quickstart, full CLI reference (every subcommand with copy-pasteable examples), and programmatic patterns for `WebhookReceiver`, `simulate.sign`, and `simulate.deliver` in pytest fixtures and offline development. Includes a troubleshooting appendix for the common gotchas (signature drift, missing extra, one-endpoint-per-user, etc.). - `README.md` — new "Webhook Tooling" section under Advanced Features with a one-paragraph overview, the install one-liner, the seven CLI subcommands at a glance, and the bare-minimum receiver pattern. WEBHOOKS.md added to the Documentation index. - `docs/API_REFERENCE.md` — filled in `get_webhook_subscription` (previously missing); replaced the hand-rolled signature snippet with a pointer to `tango.webhooks.verify_signature`; added a new "Webhook tooling (`tango.webhooks`)" section that catalogs every importable from the new subpackage (signing helpers, WebhookReceiver, Delivery, sign, SignedRequest, simulate.deliver, CLI entry point) with constructor tables and short examples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Public-audience material shouldn't compare the SDK to a third-party tool — describe what the CLI does without the analogy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a complete webhook tooling surface to
tango-pythonfor developers building integrations against the Tango API — signing helpers, a local receiver class, a command-line tool, and management commands for the underlying endpoints and subscriptions.A dev with
TANGO_API_KEYcan now go from zero to receiving real Tango deliveries entirely from the shell, and can develop / test their handler offline with no Tango involvement at all.What ships
tango.webhookssubpackageverify_signature,generate_signature,parse_signature_header. Mirrors the canonical Tango server scheme (X-Tango-Signature: sha256=<hex>HMAC-SHA256 over raw body) byte-for-byte.WebhookReceiver— context-manager-friendly local HTTP receiver for tests and development. Verifies signatures, optionalforward_tomirroring, in-memory delivery history with cap, optionalon_deliverycallback. Returns JSON error bodies ({"ok": false, "error": "invalid_signature"}) instead of stdlib HTML pages.simulate.sign(payload, secret) -> SignedRequest— produce the exact wire form a Tango delivery would have. Useful in pytest fixtures.simulate.deliver(...)— sign and POST to a target URL.tango[webhooks]extraA new optional extra (`pip install 'tango-python[webhooks]'`, adds `click`) installs a `tango` console script with the full webhook lifecycle:
```bash
Discovery
tango webhooks list-event-types
tango webhooks fetch-sample --event-type entities.updated
Local development
tango webhooks listen --port 8011 --secret $SECRET
tango webhooks simulate --secret $SECRET --event-type entities.updated # sign+print
tango webhooks simulate --secret $SECRET --event-type entities.updated
--to http://127.0.0.1:8011/tango/webhooks # also POST
Configuration (the piece that previously required dropping into Python)
tango webhooks endpoints create|list|get|delete
tango webhooks subscriptions create|list|get|delete
Real end-to-end test
tango webhooks trigger
```
Documentation
Why
Devs integrating Tango webhooks shouldn't have to clone the Tango server repo to find a test harness. The tooling that lived in `tango/tools/webhook_lab/` was already half-duplicating SDK calls (`client.py` reimplemented `/api/webhooks/subscriptions/`); putting the tooling alongside the SDK lets it use the SDK directly and ships a cohesive integration story for partner developers.
Test plan
Scope notes / decisions to confirm
Risks
Next steps
MAKEGOV_BOT_SIGNATURE