fygaro-webhook


Namefygaro-webhook JSON
Version 1.2.0 PyPI version JSON
download
home_pageNone
SummaryWebhook signature verification for Fygaro
upload_time2025-07-11 00:25:14
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT License Copyright (c) 2025 Fygaro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords fygaro webhook signature
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # fygaro-webhook

> **Webhook signature verification for Fygaro — pure Python stdlib, zero runtime deps**

This helper validates the `Fygaro-Signature` header of incoming webhooks.
It supports secret rotation (multiple active secrets), deterministic unit‑testing, and is ready for future
hash algorithms.

---

## Installation

```bash
pip install fygaro-webhook
```

*Requires Python ≥ 3.8.*

---

## Quick start

```python
from fygaro.webhook import FygaroWebhookValidator

validator = FygaroWebhookValidator(
    secrets=[
        "my-primary-secret",  # str or bytes
        # "my-previous-secret",   # include during rotation windows
    ]
)

if not validator.verify_signature(
    signature_header=request.headers["Fygaro-Signature"],
    body=request.body,  # raw bytes exactly as sent
):
    raise ValueError("Invalid signature")

# …process JSON, return 200…
```

### Opt-in: detailed error handling

```python
from fygaro.webhook import FygaroWebhookValidator, SignatureVerificationError

validator = FygaroWebhookValidator(
    secrets=["my-primary-secret"],
    raise_exceptions=True,  # Opt-in to detailed errors
)

try:
    validator.verify_signature(
        signature_header=request.headers["Fygaro-Signature"],
        body=request.body,
    )

except SignatureVerificationError as exc:
    logger.warning("Webhook rejected: %s", exc)
    return 400
```

---

## API reference

### `class FygaroWebhookValidator`

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `secrets` | `Sequence[str \| bytes]` | ✔ | — | One or more active webhook secrets. Provide **all currently valid** secrets during a rotation window. Each secret can be a UTF‑8 `str` or raw `bytes`. |
| `max_age` | `int` | ✖ | `300` | Maximum allowable clock skew (in seconds) between the timestamp in the header and the server time. A low value mitigates replay attacks |
| `unsafe_skip_ts_validation` | `bool` | ✖ | `False` | **Test only.** When `True`, the timestamp‑freshness check is skipped and a `RuntimeWarning` is emitted on instantiation. Never enable in production. |
| `raise_exceptions` | `bool` | ✖ | `False` | When True, verify_signature raises specific subclasses of SignatureVerificationError instead of returning False. |

---

#### `validator.verify_signature(signature_header: str, body: bytes) -> bool`

| Argument | Type | Description |
|----------|------|-------------|
| `signature_header` | `str` | The exact value of the incoming **Fygaro‑Signature** HTTP header. |
| `body` | `bytes` | The unmodified request body (raw bytes). **Do not** `.decode()` or re‑serialize. |

Return value:

* `True` — signature is valid **and** timestamp is within `max_age` (unless skipped).
* `False` — signature mismatch, stale timestamp, or malformed header.

### Exceptions exposed at package root

* `SignatureVerificationError` – base class
* `MissingHeaderError`
* `TimestampInvalidError`
* `TimestampExpiredError`
* `SignatureMismatchError`

---

## Writing deterministic unit tests

To keep fixtures stable you can bypass the timestamp‑freshness check **without** touching production code:

```python
validator = FygaroWebhookValidator(
    secrets=[b"test-secret"],
    unsafe_skip_ts_validation=True,  # ← test‑only flag
)
```

*The first instance created with `unsafe_skip_ts_validation=True` emits a
`RuntimeWarning` to remind you that this path is unsafe for live traffic.*

---

## License

MIT © Fygaro — support: [support@fygaro.com](mailto:support@fygaro.com)

# Changelog – @fygaro/webhook

## [1.2.0] – 2025-07-10
### Added
* `raise_exceptions` constructor flag – opt-in detailed failure reasons without
  breaking the legacy `True/False` return contract.

---

## [1.1.0] – 2025-07-10
### Added
* `unsafe_skip_ts_validation` constructor flag that bypasses
  the timestamp-freshness check.
  *Meant **only** for local/unit tests; not safe for production.*
  Emits a **`RuntimeWarning`** the first time an instance is created with the flag
  enabled, to minimise accidental misuse.

---

## [1.0.0] – 2025-06-19
### Added
* Initial release with `FygaroWebhookValidator` class.
* Supports multiple secrets & multiple `v1=` hashes.
* Constant-time compare and configurable timestamp window.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fygaro-webhook",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "fygaro, webhook, signature",
    "author": null,
    "author_email": "Fygaro <support@fygaro.com>",
    "download_url": "https://files.pythonhosted.org/packages/4d/1a/2e486f2f43fcf3540e72055440caf1220ceb964a63f0ab3ee4ba4afe9f1d/fygaro_webhook-1.2.0.tar.gz",
    "platform": null,
    "description": "# fygaro-webhook\n\n> **Webhook signature verification for Fygaro \u2014 pure Python stdlib, zero runtime deps**\n\nThis helper validates the `Fygaro-Signature` header of incoming webhooks.\nIt supports secret rotation (multiple active secrets), deterministic unit\u2011testing, and is ready for future\nhash algorithms.\n\n---\n\n## Installation\n\n```bash\npip install fygaro-webhook\n```\n\n*Requires Python\u00a0\u2265\u00a03.8.*\n\n---\n\n## Quick start\n\n```python\nfrom fygaro.webhook import FygaroWebhookValidator\n\nvalidator = FygaroWebhookValidator(\n    secrets=[\n        \"my-primary-secret\",  # str or bytes\n        # \"my-previous-secret\",   # include during rotation windows\n    ]\n)\n\nif not validator.verify_signature(\n    signature_header=request.headers[\"Fygaro-Signature\"],\n    body=request.body,  # raw bytes exactly as sent\n):\n    raise ValueError(\"Invalid signature\")\n\n# \u2026process JSON, return 200\u2026\n```\n\n### Opt-in: detailed error handling\n\n```python\nfrom fygaro.webhook import FygaroWebhookValidator, SignatureVerificationError\n\nvalidator = FygaroWebhookValidator(\n    secrets=[\"my-primary-secret\"],\n    raise_exceptions=True,  # Opt-in to detailed errors\n)\n\ntry:\n    validator.verify_signature(\n        signature_header=request.headers[\"Fygaro-Signature\"],\n        body=request.body,\n    )\n\nexcept SignatureVerificationError as exc:\n    logger.warning(\"Webhook rejected: %s\", exc)\n    return 400\n```\n\n---\n\n## API reference\n\n### `class FygaroWebhookValidator`\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `secrets` | `Sequence[str \\| bytes]` | \u2714 | \u2014 | One or more active webhook secrets. Provide **all currently valid** secrets during a rotation window. Each secret can be a UTF\u20118 `str` or raw `bytes`. |\n| `max_age` | `int` | \u2716 | `300` | Maximum allowable clock skew (in seconds) between the timestamp in the header and the server time. A low value mitigates replay attacks |\n| `unsafe_skip_ts_validation` | `bool` | \u2716 | `False` | **Test only.** When `True`, the timestamp\u2011freshness check is skipped and a `RuntimeWarning` is emitted on instantiation. Never enable in production. |\n| `raise_exceptions` | `bool` | \u2716 | `False` | When True, verify_signature raises specific subclasses of SignatureVerificationError instead of returning False. |\n\n---\n\n#### `validator.verify_signature(signature_header: str, body: bytes) -> bool`\n\n| Argument | Type | Description |\n|----------|------|-------------|\n| `signature_header` | `str` | The exact value of the incoming **Fygaro\u2011Signature** HTTP header. |\n| `body` | `bytes` | The unmodified request body (raw bytes). **Do not** `.decode()` or re\u2011serialize. |\n\nReturn value:\n\n* `True` \u2014 signature is valid **and** timestamp is within `max_age` (unless skipped).\n* `False` \u2014 signature mismatch, stale timestamp, or malformed header.\n\n### Exceptions exposed at package root\n\n* `SignatureVerificationError` \u2013 base class\n* `MissingHeaderError`\n* `TimestampInvalidError`\n* `TimestampExpiredError`\n* `SignatureMismatchError`\n\n---\n\n## Writing deterministic unit tests\n\nTo keep fixtures stable you can bypass the timestamp\u2011freshness check **without** touching production code:\n\n```python\nvalidator = FygaroWebhookValidator(\n    secrets=[b\"test-secret\"],\n    unsafe_skip_ts_validation=True,  # \u2190 test\u2011only flag\n)\n```\n\n*The first instance created with `unsafe_skip_ts_validation=True` emits a\n`RuntimeWarning` to remind you that this path is unsafe for live traffic.*\n\n---\n\n## License\n\nMIT \u00a9 Fygaro \u2014 support: [support@fygaro.com](mailto:support@fygaro.com)\n\n# Changelog \u2013 @fygaro/webhook\n\n## [1.2.0] \u2013 2025-07-10\n### Added\n* `raise_exceptions` constructor flag \u2013 opt-in detailed failure reasons without\n  breaking the legacy `True/False` return contract.\n\n---\n\n## [1.1.0] \u2013 2025-07-10\n### Added\n* `unsafe_skip_ts_validation` constructor flag that bypasses\n  the timestamp-freshness check.\n  *Meant **only** for local/unit tests; not safe for production.*\n  Emits a **`RuntimeWarning`** the first time an instance is created with the flag\n  enabled, to minimise accidental misuse.\n\n---\n\n## [1.0.0] \u2013 2025-06-19\n### Added\n* Initial release with `FygaroWebhookValidator` class.\n* Supports multiple secrets & multiple `v1=` hashes.\n* Constant-time compare and configurable timestamp window.\n",
    "bugtrack_url": null,
    "license": "MIT License\n        \n        Copyright (c) 2025 Fygaro\n        \n        Permission is hereby granted, free of charge, to any person obtaining a copy\n        of this software and associated documentation files (the \"Software\"), to deal\n        in the Software without restriction, including without limitation the rights\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the Software is\n        furnished to do so, subject to the following conditions:\n        \n        The above copyright notice and this permission notice shall be included in all\n        copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n        SOFTWARE.\n        ",
    "summary": "Webhook signature verification for Fygaro",
    "version": "1.2.0",
    "project_urls": {
        "Homepage": "https://fygaro.com"
    },
    "split_keywords": [
        "fygaro",
        " webhook",
        " signature"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "857a6f4fb60a7e8a7f864e9d2df4e655ce40f6505eb990345891699cf0b042de",
                "md5": "345e49969355461a0476c4f000813c64",
                "sha256": "411e32d20c39508980e89c6720feb3d9cacc2a7c0675d3ea4d347cbc574ced7e"
            },
            "downloads": -1,
            "filename": "fygaro_webhook-1.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "345e49969355461a0476c4f000813c64",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 7531,
            "upload_time": "2025-07-11T00:25:12",
            "upload_time_iso_8601": "2025-07-11T00:25:12.534718Z",
            "url": "https://files.pythonhosted.org/packages/85/7a/6f4fb60a7e8a7f864e9d2df4e655ce40f6505eb990345891699cf0b042de/fygaro_webhook-1.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4d1a2e486f2f43fcf3540e72055440caf1220ceb964a63f0ab3ee4ba4afe9f1d",
                "md5": "8438d7d35bbae004f85356c6fde05a65",
                "sha256": "abffbff5692578fe3b605ad2cc0cdadd80e257b88f21bb47efae4578c8defe5b"
            },
            "downloads": -1,
            "filename": "fygaro_webhook-1.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "8438d7d35bbae004f85356c6fde05a65",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 7960,
            "upload_time": "2025-07-11T00:25:14",
            "upload_time_iso_8601": "2025-07-11T00:25:14.011292Z",
            "url": "https://files.pythonhosted.org/packages/4d/1a/2e486f2f43fcf3540e72055440caf1220ceb964a63f0ab3ee4ba4afe9f1d/fygaro_webhook-1.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-11 00:25:14",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "fygaro-webhook"
}
        
Elapsed time: 0.42766s