Metadata-Version: 2.5
Name: tradingsignalbot
Version: 0.1.0
Summary: Verify TradingSignalBot webhook signatures (HMAC-SHA256, constant-time, timestamp window). Standard library only. The full API client comes when the sandbox opens.
Project-URL: Homepage, https://tradingsignalbot.com/api/?via=tsb-sdk
Project-URL: Source, https://github.com/TradingSignalBot/sdk/tree/main/python
Project-URL: Issues, https://github.com/TradingSignalBot/sdk/issues
Author: TradingSignalBot
License-Expression: MIT
License-File: LICENSE
Keywords: forex signals api,hmac,signature,telegram signal bot,trading signals,tradingsignalbot,webhook
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Security :: Cryptography
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# tradingsignalbot

The Python client for the [TradingSignalBot API](https://tradingsignalbot.com/api/?via=tsb-sdk).

**The API is a preview.** It is not live yet, so this package holds only the part you need first: **verifying the signature of a webhook delivery**, so you know an event came from TradingSignalBot and was not changed or replayed. The full client (render a chart from your own levels, register webhooks, read signals) comes here when the sandbox opens. [Get early access](https://tradingsignalbot.com/?for=developers&via=tsb-sdk#start) to get sandbox keys first.

```sh
pip install tradingsignalbot
```

Standard library only (`hmac.compare_digest`), Python 3.8+, typed.

## Verify a delivery

```python
from flask import Flask, request, abort
from tradingsignalbot import verify_webhook, WebhookVerificationError

app = Flask(__name__)

@app.post("/hooks/signals")
def signals():
    try:
        event = verify_webhook(request.get_data(), request.headers.get("TSB-Signature"), SECRET)
    except WebhookVerificationError as e:
        abort(400, e.code)
    return "", 204
```

Pass the **raw body**, exactly as it arrived. Parsed and re-serialised JSON will not match.

## The scheme

The scheme, as the [API page](https://tradingsignalbot.com/api/?via=tsb-sdk) states it:

| | |
| --- | --- |
| Header | `TSB-Signature: t=<timestamp>,v1=<signature>` (shown on the preview page) |
| Timestamp | Unix seconds (the preview's example uses the event's time in seconds) |
| Signed | `"<timestamp>.<raw body>"`, HMAC-SHA256 with your webhook secret |
| Signature | lowercase hex, 64 characters |
| Window | the timestamp within 300 seconds of your clock, against replays *(`tolerance` changes it, 0 turns it off)* |
| Several `v1=` | any one matching is enough, for secret rotation |

The repository's `test-vectors.json` holds the cases both packages are tested against.

Both packages compare in constant time and check every signature and every secret, so a mismatch takes as long as a match.

## API

| Function | Does |
| --- | --- |
| `verify_webhook(body, header, secret, tolerance=300, now=None)` | Verifies, returns the parsed event |
| `verify_signature(...)` | Verifies, returns the timestamp; raises `WebhookVerificationError` |
| `is_valid_signature(...)` | `True` / `False` |
| `sign_payload(body, secret, timestamp=None)` | The header TradingSignalBot would send: for your tests |
| `compute_signature(body, secret, t)` | The hex HMAC |
| `parse_signature_header(header)` | `(timestamp, signatures)` |

`secret` can be a list while you rotate it. `WebhookVerificationError.code` is one of `missing_header`, `bad_header`, `no_signature`, `timestamp`, `mismatch`, `bad_json`.

## Licence

MIT. Source and the JavaScript package: [github.com/TradingSignalBot/sdk](https://github.com/TradingSignalBot/sdk).

---

Part of the Forexia network: InterestRateDecision.com · Forexia Academy · [TradingSignalBot](https://tradingsignalbot.com/?via=tsb-sdk) · PropSim · OpenCharts
