webhooks-cc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webhooks_cc-0.1.0/.gitignore +6 -0
- webhooks_cc-0.1.0/LICENSE +21 -0
- webhooks_cc-0.1.0/PKG-INFO +154 -0
- webhooks_cc-0.1.0/README.md +130 -0
- webhooks_cc-0.1.0/pyproject.toml +67 -0
- webhooks_cc-0.1.0/src/webhooks_cc/__init__.py +144 -0
- webhooks_cc-0.1.0/src/webhooks_cc/_crypto.py +255 -0
- webhooks_cc-0.1.0/src/webhooks_cc/_sentinel.py +16 -0
- webhooks_cc-0.1.0/src/webhooks_cc/_sse.py +45 -0
- webhooks_cc-0.1.0/src/webhooks_cc/client.py +1287 -0
- webhooks_cc-0.1.0/src/webhooks_cc/durations.py +56 -0
- webhooks_cc-0.1.0/src/webhooks_cc/email.py +78 -0
- webhooks_cc-0.1.0/src/webhooks_cc/errors.py +99 -0
- webhooks_cc-0.1.0/src/webhooks_cc/matchers.py +242 -0
- webhooks_cc-0.1.0/src/webhooks_cc/models.py +403 -0
- webhooks_cc-0.1.0/src/webhooks_cc/py.typed +0 -0
- webhooks_cc-0.1.0/src/webhooks_cc/pytest_plugin.py +43 -0
- webhooks_cc-0.1.0/src/webhooks_cc/templates.py +2483 -0
- webhooks_cc-0.1.0/src/webhooks_cc/testing.py +135 -0
- webhooks_cc-0.1.0/src/webhooks_cc/verify.py +1297 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mads Sauer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: webhooks-cc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for webhooks.cc: capture, wait for, sign and verify webhooks and test emails
|
|
5
|
+
Project-URL: Homepage, https://webhooks.cc
|
|
6
|
+
Project-URL: Documentation, https://github.com/kroqdotdev/webhooks-cc/tree/main/packages/sdk-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/kroqdotdev/webhooks-cc
|
|
8
|
+
Author: webhooks.cc
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: email,github,pytest,signature,stripe,testing,webhooks
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: Pytest
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: httpx<1,>=0.27
|
|
21
|
+
Provides-Extra: crypto
|
|
22
|
+
Requires-Dist: cryptography>=42; extra == 'crypto'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# webhooks-cc
|
|
26
|
+
|
|
27
|
+
Python SDK for [webhooks.cc](https://webhooks.cc): capture webhooks and email in your tests, wait for them, send signed webhooks to your own handlers, and verify provider signatures.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install webhooks-cc
|
|
31
|
+
pip install "webhooks-cc[crypto]" # also verify Discord (Ed25519) and PayPal (RSA) signatures
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Python 3.10 or later. Get an API key at [webhooks.cc/account/api-keys](https://webhooks.cc/account/api-keys) and set `WHK_API_KEY`, or pass `api_key=`.
|
|
35
|
+
|
|
36
|
+
## Wait for a webhook
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from webhooks_cc import WebhooksCC, match_body_path
|
|
40
|
+
|
|
41
|
+
with WebhooksCC() as client:
|
|
42
|
+
endpoint = client.endpoints.create(name="checkout test")
|
|
43
|
+
|
|
44
|
+
trigger_checkout(webhook_url=endpoint.url)
|
|
45
|
+
|
|
46
|
+
request = client.requests.wait_for(
|
|
47
|
+
endpoint.slug,
|
|
48
|
+
timeout="30s",
|
|
49
|
+
match=match_body_path("type", "checkout.session.completed"),
|
|
50
|
+
)
|
|
51
|
+
assert request.json()["data"]["object"]["amount_total"] == 2000
|
|
52
|
+
|
|
53
|
+
client.endpoints.delete(endpoint.slug)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Durations take `"500ms"`, `"30s"`, `"5m"`, `"2h"`, `"7d"`, a `timedelta`, or a number of milliseconds (the unit every webhooks.cc SDK uses, so prefer a string where you mean seconds). Times such as `since` and `Request.received_at` are milliseconds since the epoch.
|
|
57
|
+
|
|
58
|
+
Matchers combine with `match_all()` and `match_any()`, and any function that takes a `Request` and returns a bool works too: `match_method`, `match_path` (globs with `*` and `**`), `match_header`, `match_query_param`, `match_body_path`, `match_body_subset`, `match_content_type`, `match_verified`, `match_unverified` and `match_email`.
|
|
59
|
+
|
|
60
|
+
## pytest
|
|
61
|
+
|
|
62
|
+
Installing the package adds two fixtures. Tests that use them are skipped when `WHK_API_KEY` is not set.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
def test_sends_a_receipt_webhook(webhooks_cc, webhooks_cc_endpoint):
|
|
66
|
+
place_order(webhook_url=webhooks_cc_endpoint.url)
|
|
67
|
+
|
|
68
|
+
request = webhooks_cc.requests.wait_for(webhooks_cc_endpoint.slug, timeout="30s")
|
|
69
|
+
assert request.method == "POST"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`webhooks_cc` is a client for the session; `webhooks_cc_endpoint` is a new endpoint for each test, deleted afterwards. Outside pytest, `temporary_endpoint(client)` is the same as a context manager, and `capture_during(client, action)` runs `action(endpoint)` and returns what it sent.
|
|
73
|
+
|
|
74
|
+
## Test email: codes and links
|
|
75
|
+
|
|
76
|
+
Every endpoint receives email at `{slug}@mailhooks.cc`, and at `{slug}+{tag}@mailhooks.cc` for any tag. Use a fresh tag per test run to find your own email. webhooks.cc finds the one-time codes and links in each email, the same ones its dashboard shows.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import uuid
|
|
80
|
+
from webhooks_cc import extract_code, extract_link
|
|
81
|
+
|
|
82
|
+
run = f"signup-{uuid.uuid4().hex[:8]}"
|
|
83
|
+
sign_up(email=client.emails.address(endpoint, run))
|
|
84
|
+
|
|
85
|
+
email = client.emails.wait_for(endpoint.slug, tag=run, subject="Confirm your email")
|
|
86
|
+
code = extract_code(email) # "482913"
|
|
87
|
+
link = extract_link(email) # "https://app.example.com/confirm?token=..."
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`email.email` holds the parsed message: `subject`, `from_`, `to`, `text`, `html`, attachments, and the SPF, DKIM and DMARC results.
|
|
91
|
+
|
|
92
|
+
## Send signed webhooks to your handler
|
|
93
|
+
|
|
94
|
+
Test your own webhook handler, on localhost or anywhere else, with requests signed the way the provider signs them:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
response = client.send_to(
|
|
98
|
+
"http://localhost:8000/webhooks/stripe",
|
|
99
|
+
provider="stripe",
|
|
100
|
+
secret="whsec_test_secret",
|
|
101
|
+
template="checkout.session.completed",
|
|
102
|
+
)
|
|
103
|
+
assert response.status_code == 200
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Leave out `body` to send the provider's sample payload, or pass your own: a string or bytes go as they are, anything else as JSON. `timestamp` (seconds) and `message_id` make the signature repeatable. `build_request()` returns the method, URL, headers and body without sending. Redirects are not followed, so signed requests only ever reach the host you named. `client.endpoints.send_template(slug, ...)` sends the same to a webhooks.cc endpoint. `TEMPLATE_PROVIDERS` lists the 34 providers.
|
|
107
|
+
|
|
108
|
+
## Verify signatures
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from webhooks_cc import verify_signature
|
|
112
|
+
|
|
113
|
+
valid = verify_signature(
|
|
114
|
+
"stripe",
|
|
115
|
+
body=request.body, # the exact bytes or text received
|
|
116
|
+
headers=request.headers, # any mapping; names match in any case
|
|
117
|
+
secret="whsec_...",
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Twilio, Square and HubSpot also need `url`; HubSpot also `method`. Discord takes `public_key`. One function per provider is in `webhooks_cc.verify`.
|
|
122
|
+
|
|
123
|
+
When webhooks.cc forwards requests or email to your server, check that they came from it with the endpoint's forwarding secret (`client.forwarding.secret(slug)`):
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from webhooks_cc import WebhookVerificationError, verify_forwarded_email
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
event = verify_forwarded_email(body, headers, secret)
|
|
130
|
+
except WebhookVerificationError as error:
|
|
131
|
+
return 400 if error.code == "invalid_payload" else 401
|
|
132
|
+
print(event["data"]["subject"])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`verify_forwarded_request()` checks a request forwarded as JSON, and `verify_relayed_request()` one relayed as it was received.
|
|
136
|
+
|
|
137
|
+
## Stream requests as they arrive
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
for request in client.requests.subscribe(endpoint.slug, timeout="10m", reconnect=True):
|
|
141
|
+
print(request.method, request.path)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Errors
|
|
145
|
+
|
|
146
|
+
API errors raise `WebhooksCCError` with a `status_code`, or one of its subclasses: `UnauthorizedError` (401), `NotFoundError` (404), `RateLimitError` (429, with `retry_after`) and `TimeoutError`, which is also Python's built-in `TimeoutError`. Network failures raise httpx's exceptions. Retries are off unless you pass `retry=RetryOptions(max_attempts=3)`.
|
|
147
|
+
|
|
148
|
+
## Same behaviour as the TypeScript SDK
|
|
149
|
+
|
|
150
|
+
This SDK passes the same conformance vectors as [`@webhooks-cc/sdk`](https://www.npmjs.com/package/@webhooks-cc/sdk): signing, signature verification, forwarding signatures, matchers and durations. They live in [`packages/sdk/conformance`](https://github.com/kroqdotdev/webhooks-cc/tree/main/packages/sdk/conformance).
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# webhooks-cc
|
|
2
|
+
|
|
3
|
+
Python SDK for [webhooks.cc](https://webhooks.cc): capture webhooks and email in your tests, wait for them, send signed webhooks to your own handlers, and verify provider signatures.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install webhooks-cc
|
|
7
|
+
pip install "webhooks-cc[crypto]" # also verify Discord (Ed25519) and PayPal (RSA) signatures
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Python 3.10 or later. Get an API key at [webhooks.cc/account/api-keys](https://webhooks.cc/account/api-keys) and set `WHK_API_KEY`, or pass `api_key=`.
|
|
11
|
+
|
|
12
|
+
## Wait for a webhook
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from webhooks_cc import WebhooksCC, match_body_path
|
|
16
|
+
|
|
17
|
+
with WebhooksCC() as client:
|
|
18
|
+
endpoint = client.endpoints.create(name="checkout test")
|
|
19
|
+
|
|
20
|
+
trigger_checkout(webhook_url=endpoint.url)
|
|
21
|
+
|
|
22
|
+
request = client.requests.wait_for(
|
|
23
|
+
endpoint.slug,
|
|
24
|
+
timeout="30s",
|
|
25
|
+
match=match_body_path("type", "checkout.session.completed"),
|
|
26
|
+
)
|
|
27
|
+
assert request.json()["data"]["object"]["amount_total"] == 2000
|
|
28
|
+
|
|
29
|
+
client.endpoints.delete(endpoint.slug)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Durations take `"500ms"`, `"30s"`, `"5m"`, `"2h"`, `"7d"`, a `timedelta`, or a number of milliseconds (the unit every webhooks.cc SDK uses, so prefer a string where you mean seconds). Times such as `since` and `Request.received_at` are milliseconds since the epoch.
|
|
33
|
+
|
|
34
|
+
Matchers combine with `match_all()` and `match_any()`, and any function that takes a `Request` and returns a bool works too: `match_method`, `match_path` (globs with `*` and `**`), `match_header`, `match_query_param`, `match_body_path`, `match_body_subset`, `match_content_type`, `match_verified`, `match_unverified` and `match_email`.
|
|
35
|
+
|
|
36
|
+
## pytest
|
|
37
|
+
|
|
38
|
+
Installing the package adds two fixtures. Tests that use them are skipped when `WHK_API_KEY` is not set.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
def test_sends_a_receipt_webhook(webhooks_cc, webhooks_cc_endpoint):
|
|
42
|
+
place_order(webhook_url=webhooks_cc_endpoint.url)
|
|
43
|
+
|
|
44
|
+
request = webhooks_cc.requests.wait_for(webhooks_cc_endpoint.slug, timeout="30s")
|
|
45
|
+
assert request.method == "POST"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`webhooks_cc` is a client for the session; `webhooks_cc_endpoint` is a new endpoint for each test, deleted afterwards. Outside pytest, `temporary_endpoint(client)` is the same as a context manager, and `capture_during(client, action)` runs `action(endpoint)` and returns what it sent.
|
|
49
|
+
|
|
50
|
+
## Test email: codes and links
|
|
51
|
+
|
|
52
|
+
Every endpoint receives email at `{slug}@mailhooks.cc`, and at `{slug}+{tag}@mailhooks.cc` for any tag. Use a fresh tag per test run to find your own email. webhooks.cc finds the one-time codes and links in each email, the same ones its dashboard shows.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import uuid
|
|
56
|
+
from webhooks_cc import extract_code, extract_link
|
|
57
|
+
|
|
58
|
+
run = f"signup-{uuid.uuid4().hex[:8]}"
|
|
59
|
+
sign_up(email=client.emails.address(endpoint, run))
|
|
60
|
+
|
|
61
|
+
email = client.emails.wait_for(endpoint.slug, tag=run, subject="Confirm your email")
|
|
62
|
+
code = extract_code(email) # "482913"
|
|
63
|
+
link = extract_link(email) # "https://app.example.com/confirm?token=..."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`email.email` holds the parsed message: `subject`, `from_`, `to`, `text`, `html`, attachments, and the SPF, DKIM and DMARC results.
|
|
67
|
+
|
|
68
|
+
## Send signed webhooks to your handler
|
|
69
|
+
|
|
70
|
+
Test your own webhook handler, on localhost or anywhere else, with requests signed the way the provider signs them:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
response = client.send_to(
|
|
74
|
+
"http://localhost:8000/webhooks/stripe",
|
|
75
|
+
provider="stripe",
|
|
76
|
+
secret="whsec_test_secret",
|
|
77
|
+
template="checkout.session.completed",
|
|
78
|
+
)
|
|
79
|
+
assert response.status_code == 200
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Leave out `body` to send the provider's sample payload, or pass your own: a string or bytes go as they are, anything else as JSON. `timestamp` (seconds) and `message_id` make the signature repeatable. `build_request()` returns the method, URL, headers and body without sending. Redirects are not followed, so signed requests only ever reach the host you named. `client.endpoints.send_template(slug, ...)` sends the same to a webhooks.cc endpoint. `TEMPLATE_PROVIDERS` lists the 34 providers.
|
|
83
|
+
|
|
84
|
+
## Verify signatures
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from webhooks_cc import verify_signature
|
|
88
|
+
|
|
89
|
+
valid = verify_signature(
|
|
90
|
+
"stripe",
|
|
91
|
+
body=request.body, # the exact bytes or text received
|
|
92
|
+
headers=request.headers, # any mapping; names match in any case
|
|
93
|
+
secret="whsec_...",
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Twilio, Square and HubSpot also need `url`; HubSpot also `method`. Discord takes `public_key`. One function per provider is in `webhooks_cc.verify`.
|
|
98
|
+
|
|
99
|
+
When webhooks.cc forwards requests or email to your server, check that they came from it with the endpoint's forwarding secret (`client.forwarding.secret(slug)`):
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from webhooks_cc import WebhookVerificationError, verify_forwarded_email
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
event = verify_forwarded_email(body, headers, secret)
|
|
106
|
+
except WebhookVerificationError as error:
|
|
107
|
+
return 400 if error.code == "invalid_payload" else 401
|
|
108
|
+
print(event["data"]["subject"])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`verify_forwarded_request()` checks a request forwarded as JSON, and `verify_relayed_request()` one relayed as it was received.
|
|
112
|
+
|
|
113
|
+
## Stream requests as they arrive
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
for request in client.requests.subscribe(endpoint.slug, timeout="10m", reconnect=True):
|
|
117
|
+
print(request.method, request.path)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Errors
|
|
121
|
+
|
|
122
|
+
API errors raise `WebhooksCCError` with a `status_code`, or one of its subclasses: `UnauthorizedError` (401), `NotFoundError` (404), `RateLimitError` (429, with `retry_after`) and `TimeoutError`, which is also Python's built-in `TimeoutError`. Network failures raise httpx's exceptions. Retries are off unless you pass `retry=RetryOptions(max_attempts=3)`.
|
|
123
|
+
|
|
124
|
+
## Same behaviour as the TypeScript SDK
|
|
125
|
+
|
|
126
|
+
This SDK passes the same conformance vectors as [`@webhooks-cc/sdk`](https://www.npmjs.com/package/@webhooks-cc/sdk): signing, signature verification, forwarding signatures, matchers and durations. They live in [`packages/sdk/conformance`](https://github.com/kroqdotdev/webhooks-cc/tree/main/packages/sdk/conformance).
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "webhooks-cc"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python SDK for webhooks.cc: capture, wait for, sign and verify webhooks and test emails"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
authors = [{ name = "webhooks.cc" }]
|
|
10
|
+
keywords = ["webhooks", "testing", "stripe", "github", "signature", "email", "pytest"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Framework :: Pytest",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: Software Development :: Testing",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
# Discord (Ed25519) and PayPal (RSA) signatures need public-key crypto.
|
|
24
|
+
crypto = ["cryptography>=42"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://webhooks.cc"
|
|
28
|
+
Documentation = "https://github.com/kroqdotdev/webhooks-cc/tree/main/packages/sdk-python#readme"
|
|
29
|
+
Repository = "https://github.com/kroqdotdev/webhooks-cc"
|
|
30
|
+
|
|
31
|
+
[project.entry-points.pytest11]
|
|
32
|
+
webhooks_cc = "webhooks_cc.pytest_plugin"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"cryptography>=42",
|
|
37
|
+
"mypy>=1.13",
|
|
38
|
+
"pytest>=8",
|
|
39
|
+
"ruff>=0.8",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling>=1.27"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/webhooks_cc"]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.sdist]
|
|
50
|
+
# The tests read packages/sdk/conformance, which only the repository has.
|
|
51
|
+
include = ["src", "README.md", "LICENSE"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
addopts = "-p no:cacheprovider"
|
|
56
|
+
|
|
57
|
+
[tool.ruff]
|
|
58
|
+
line-length = 100
|
|
59
|
+
target-version = "py310"
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint]
|
|
62
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
63
|
+
|
|
64
|
+
[tool.mypy]
|
|
65
|
+
python_version = "3.10"
|
|
66
|
+
strict = true
|
|
67
|
+
files = ["src"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""webhooks.cc SDK for Python.
|
|
2
|
+
|
|
3
|
+
Capture webhooks and email in tests, wait for them, send signed webhooks to
|
|
4
|
+
your own handlers, and verify provider and forwarding signatures.
|
|
5
|
+
|
|
6
|
+
.. code-block:: python
|
|
7
|
+
|
|
8
|
+
from webhooks_cc import WebhooksCC, match_body_path
|
|
9
|
+
|
|
10
|
+
with WebhooksCC() as client: # reads WHK_API_KEY
|
|
11
|
+
endpoint = client.endpoints.create(name="checkout test")
|
|
12
|
+
request = client.requests.wait_for(
|
|
13
|
+
endpoint.slug, timeout="30s", match=match_body_path("type", "invoice.paid")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Every signature check for each provider is in :mod:`webhooks_cc.verify`.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
from .client import Emails, Endpoints, Forwarding, Requests, RetryOptions, WebhooksCC
|
|
22
|
+
from .durations import Duration, parse_duration
|
|
23
|
+
from .email import (
|
|
24
|
+
DEFAULT_EMAIL_DOMAIN,
|
|
25
|
+
email_address,
|
|
26
|
+
extract_code,
|
|
27
|
+
extract_link,
|
|
28
|
+
is_valid_email_tag,
|
|
29
|
+
)
|
|
30
|
+
from .errors import (
|
|
31
|
+
NotFoundError,
|
|
32
|
+
RateLimitError,
|
|
33
|
+
TimeoutError,
|
|
34
|
+
UnauthorizedError,
|
|
35
|
+
WebhooksCCError,
|
|
36
|
+
WebhookVerificationError,
|
|
37
|
+
)
|
|
38
|
+
from .matchers import (
|
|
39
|
+
Matcher,
|
|
40
|
+
is_email_request,
|
|
41
|
+
match_all,
|
|
42
|
+
match_any,
|
|
43
|
+
match_body_path,
|
|
44
|
+
match_body_subset,
|
|
45
|
+
match_content_type,
|
|
46
|
+
match_email,
|
|
47
|
+
match_header,
|
|
48
|
+
match_method,
|
|
49
|
+
match_path,
|
|
50
|
+
match_query_param,
|
|
51
|
+
match_unverified,
|
|
52
|
+
match_verified,
|
|
53
|
+
)
|
|
54
|
+
from .models import (
|
|
55
|
+
BuiltRequest,
|
|
56
|
+
EmailAddress,
|
|
57
|
+
EmailAttachment,
|
|
58
|
+
EmailCapture,
|
|
59
|
+
EmailExtracts,
|
|
60
|
+
Endpoint,
|
|
61
|
+
ExtractedLink,
|
|
62
|
+
Request,
|
|
63
|
+
RequestPage,
|
|
64
|
+
SendTestEmailResult,
|
|
65
|
+
TeamShare,
|
|
66
|
+
UsageInfo,
|
|
67
|
+
)
|
|
68
|
+
from .templates import (
|
|
69
|
+
TEMPLATE_METADATA,
|
|
70
|
+
TEMPLATE_PROVIDERS,
|
|
71
|
+
VERIFY_PROVIDERS,
|
|
72
|
+
TemplateProviderInfo,
|
|
73
|
+
build_template_request,
|
|
74
|
+
)
|
|
75
|
+
from .testing import capture_during, capture_email_during, temporary_endpoint
|
|
76
|
+
from .verify import (
|
|
77
|
+
RelayedRequestInfo,
|
|
78
|
+
verify_forwarded_email,
|
|
79
|
+
verify_forwarded_request,
|
|
80
|
+
verify_relayed_request,
|
|
81
|
+
verify_signature,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
__all__ = [
|
|
85
|
+
"DEFAULT_EMAIL_DOMAIN",
|
|
86
|
+
"TEMPLATE_METADATA",
|
|
87
|
+
"TEMPLATE_PROVIDERS",
|
|
88
|
+
"VERIFY_PROVIDERS",
|
|
89
|
+
"BuiltRequest",
|
|
90
|
+
"Duration",
|
|
91
|
+
"EmailAddress",
|
|
92
|
+
"EmailAttachment",
|
|
93
|
+
"EmailCapture",
|
|
94
|
+
"EmailExtracts",
|
|
95
|
+
"Emails",
|
|
96
|
+
"Endpoint",
|
|
97
|
+
"Endpoints",
|
|
98
|
+
"ExtractedLink",
|
|
99
|
+
"Forwarding",
|
|
100
|
+
"Matcher",
|
|
101
|
+
"NotFoundError",
|
|
102
|
+
"RateLimitError",
|
|
103
|
+
"RelayedRequestInfo",
|
|
104
|
+
"Request",
|
|
105
|
+
"RequestPage",
|
|
106
|
+
"Requests",
|
|
107
|
+
"RetryOptions",
|
|
108
|
+
"SendTestEmailResult",
|
|
109
|
+
"TeamShare",
|
|
110
|
+
"TemplateProviderInfo",
|
|
111
|
+
"TimeoutError",
|
|
112
|
+
"UnauthorizedError",
|
|
113
|
+
"UsageInfo",
|
|
114
|
+
"WebhookVerificationError",
|
|
115
|
+
"WebhooksCC",
|
|
116
|
+
"WebhooksCCError",
|
|
117
|
+
"__version__",
|
|
118
|
+
"build_template_request",
|
|
119
|
+
"capture_during",
|
|
120
|
+
"capture_email_during",
|
|
121
|
+
"email_address",
|
|
122
|
+
"extract_code",
|
|
123
|
+
"extract_link",
|
|
124
|
+
"is_email_request",
|
|
125
|
+
"is_valid_email_tag",
|
|
126
|
+
"match_all",
|
|
127
|
+
"match_any",
|
|
128
|
+
"match_body_path",
|
|
129
|
+
"match_body_subset",
|
|
130
|
+
"match_content_type",
|
|
131
|
+
"match_email",
|
|
132
|
+
"match_header",
|
|
133
|
+
"match_method",
|
|
134
|
+
"match_path",
|
|
135
|
+
"match_query_param",
|
|
136
|
+
"match_unverified",
|
|
137
|
+
"match_verified",
|
|
138
|
+
"parse_duration",
|
|
139
|
+
"temporary_endpoint",
|
|
140
|
+
"verify_forwarded_email",
|
|
141
|
+
"verify_forwarded_request",
|
|
142
|
+
"verify_relayed_request",
|
|
143
|
+
"verify_signature",
|
|
144
|
+
]
|