webhooks-cc
Python SDK for webhooks.cc: capture webhooks and email in your tests, wait for them, send signed webhooks to your own handlers, and verify provider signatures.
pip install webhooks-cc
pip install "webhooks-cc[crypto]" # also verify Discord (Ed25519) and PayPal (RSA) signatures
Python 3.10 or later. Get an API key at webhooks.cc/account/api-keys and set WHK_API_KEY, or pass api_key=.
Wait for a webhook
from webhooks_cc import WebhooksCC, match_body_path
with WebhooksCC() as client:
endpoint = client.endpoints.create(name="checkout test")
trigger_checkout(webhook_url=endpoint.url)
request = client.requests.wait_for(
endpoint.slug,
timeout="30s",
match=match_body_path("type", "checkout.session.completed"),
)
assert request.json()["data"]["object"]["amount_total"] == 2000
client.endpoints.delete(endpoint.slug)
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.
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.
pytest
Installing the package adds two fixtures. Tests that use them are skipped when WHK_API_KEY is not set.
def test_sends_a_receipt_webhook(webhooks_cc, webhooks_cc_endpoint):
place_order(webhook_url=webhooks_cc_endpoint.url)
request = webhooks_cc.requests.wait_for(webhooks_cc_endpoint.slug, timeout="30s")
assert request.method == "POST"
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.
Test email: codes and links
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.
import uuid
from webhooks_cc import extract_code, extract_link
run = f"signup-{uuid.uuid4().hex[:8]}"
sign_up(email=client.emails.address(endpoint, run))
email = client.emails.wait_for(endpoint.slug, tag=run, subject="Confirm your email")
code = extract_code(email) # "482913"
link = extract_link(email) # "https://app.example.com/confirm?token=..."
email.email holds the parsed message: subject, from_, to, text, html, attachments, and the SPF, DKIM and DMARC results.
Send signed webhooks to your handler
Test your own webhook handler, on localhost or anywhere else, with requests signed the way the provider signs them:
response = client.send_to(
"http://localhost:8000/webhooks/stripe",
provider="stripe",
secret="whsec_test_secret",
template="checkout.session.completed",
)
assert response.status_code == 200
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.
Verify signatures
from webhooks_cc import verify_signature
valid = verify_signature(
"stripe",
body=request.body, # the exact bytes or text received
headers=request.headers, # any mapping; names match in any case
secret="whsec_...",
)
Twilio, Square and HubSpot also need url; HubSpot also method. Discord takes public_key. One function per provider is in webhooks_cc.verify.
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)):
from webhooks_cc import WebhookVerificationError, verify_forwarded_email
try:
event = verify_forwarded_email(body, headers, secret)
except WebhookVerificationError as error:
return 400 if error.code == "invalid_payload" else 401
print(event["data"]["subject"])
verify_forwarded_request() checks a request forwarded as JSON, and verify_relayed_request() one relayed as it was received.
Stream requests as they arrive
for request in client.requests.subscribe(endpoint.slug, timeout="10m", reconnect=True):
print(request.method, request.path)
Errors
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).
Same behaviour as the TypeScript SDK
This SDK passes the same conformance vectors as @webhooks-cc/sdk: signing, signature verification, forwarding signatures, matchers and durations. They live in packages/sdk/conformance.
License
MIT
Metadata
Release files for webhooks-cc 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| webhooks_cc-0.1.0.tar.gz | 58.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| webhooks_cc-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 123.2 kB
Release files / webhooks_cc-0.1.0.tar.gz
| Download URL | webhooks_cc-0.1.0.tar.gz |
|---|---|
| Size | 58.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c949d1e34168ace42ca0124da87e41f7c5aae80d15c8409baff41355301a8256
|
|
BLAKE2b-256 checksum How to use checksums |
19720e963f80935138d1a3c35953039e0afff7cade38f988ff71f8fb75b5e022
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / webhooks_cc-0.1.0-py3-none-any.whl
| Download URL | webhooks_cc-0.1.0-py3-none-any.whl |
|---|---|
| Size | 65.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dae9786a3e69819601a0a5e7a5d17d1189fe735f48a1d49dcbed11f33c9041f4
|
|
BLAKE2b-256 checksum How to use checksums |
feecacdb21e74b35f0d7b7b98a0f84a029cc415ee3b60dae7d8fa558a380865f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for webhooks-cc 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for webhooks_cc-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.