Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,22 @@ commet.quota.get_all(customer_id="cus_123")
## Webhook verification

```python
from commet import Webhooks
from commet import WebhookEventType, Webhooks

webhooks = Webhooks()

payload = webhooks.verify_and_parse(
event = webhooks.verify_and_parse(
raw_body=request_body,
signature=request.headers["x-commet-signature"],
secret="whsec_xxx",
)

if payload is None:
if event is None:
raise ValueError("Invalid webhook signature")

if payload["event"] == "subscription.activated":
# handle activation
pass
if event.event == WebhookEventType.SUBSCRIPTION_ACTIVATED:
data = event.as_subscription_activated()
grant_access(data.customerId, data.invoiceTotal)
```

## Context manager
Expand Down
124 changes: 124 additions & 0 deletions src/commet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
)
from ._shared import API_VERSION
from ._telemetry import register_integration
from ._webhook_shared import (
WebhookAddonRef,
WebhookBalance,
WebhookBankRef,
WebhookCardInfo,
WebhookCreditsBalance,
WebhookFeatureAccess,
WebhookPlanRef,
WebhookSeatSummary,
)
from .async_client import AsyncCommet
from .async_resources.webhooks import AsyncWebhooks
from .client import Commet
Expand Down Expand Up @@ -133,6 +143,60 @@
UsageQuota,
UsageQuotaEvent,
)
from .webhook_events import (
AddonActivatedData,
AddonDeactivatedData,
BalanceDepletedData,
BalanceLowData,
BalanceToppedUpData,
CheckoutReadyData,
CreditsDepletedData,
CreditsExpiredData,
CreditsGrantedData,
CreditsLowData,
CreditsPurchasedData,
CustomerCreatedData,
CustomerStateChangedData,
CustomerUpdatedData,
InvoiceCreatedData,
InvoiceOverdueData,
InvoiceUpcomingData,
InvoiceVoidedData,
PaymentDisputedData,
PaymentDisputeResolvedData,
PaymentFailedData,
PaymentMethodAttachedData,
PaymentMethodUpdatedData,
PaymentReceivedData,
PaymentRecoveredData,
PaymentRefundedData,
PayoutAvailableData,
PayoutCreatedData,
PayoutFailedData,
PayoutPaidData,
QuotaExceededData,
QuotaThresholdReachedData,
SeatsLimitReachedData,
SeatsUpdatedData,
SubscriptionActivatedData,
SubscriptionCanceledData,
SubscriptionCancellationRevokedData,
SubscriptionCancellationScheduledData,
SubscriptionCreatedData,
SubscriptionPastDueData,
SubscriptionPlanChangedData,
SubscriptionPlanChangeRevokedData,
SubscriptionPlanChangeScheduledData,
SubscriptionUpdatedData,
TrialCheckoutReadyData,
TrialConvertedData,
TrialExpiredData,
TrialStartedData,
TrialWillEndData,
UsageRecordedData,
WebhookEvent,
WebhookEventType,
)

try:
from importlib.metadata import version
Expand All @@ -148,17 +212,23 @@
"AddPlanPriceParamsIntroOffer",
"AddedPlanToGroup",
"Addon",
"AddonActivatedData",
"AddonDeactivatedData",
"ApiKey",
"ApiResponse",
"AsyncCommet",
"AsyncWebhooks",
"BalanceAdjustment",
"BalanceDepletedData",
"BalanceLowData",
"BalanceToppedUpData",
"BalanceTopup",
"BatchCreateCustomersParamsCustomersItem",
"BatchCreateCustomersParamsCustomersItemAddress",
"BillingInterval",
"BulkSeatUpdate",
"CanceledSubscription",
"CheckoutReadyData",
"Commet",
"CommetAPIError",
"CommetError",
Expand All @@ -176,12 +246,20 @@
"CreatedInvoice",
"CreditGrant",
"CreditPack",
"CreditsDepletedData",
"CreditsExpiredData",
"CreditsGrantedData",
"CreditsLowData",
"CreditsPurchasedData",
"Customer",
"CustomerBatch",
"CustomerBatchFailedItem",
"CustomerBatchFailedItemData",
"CustomerBatchFailedItemDataAddress",
"CustomerBatchSuccessfulItem",
"CustomerCreatedData",
"CustomerStateChangedData",
"CustomerUpdatedData",
"DefaultPlanPrice",
"DeleteResult",
"DeletedObject",
Expand All @@ -193,12 +271,28 @@
"FeatureLookup",
"FeatureType",
"Invoice",
"InvoiceCreatedData",
"InvoiceDownload",
"InvoiceLineItemsItem",
"InvoiceOverdueData",
"InvoiceStatus",
"InvoiceType",
"InvoiceUpcomingData",
"InvoiceVoidedData",
"PaymentDisputeResolvedData",
"PaymentDisputedData",
"PaymentFailedData",
"PaymentMethodAttachedData",
"PaymentMethodUpdatedData",
"PaymentReceivedData",
"PaymentRecoveredData",
"PaymentRefundedData",
"Payout",
"PayoutAvailableData",
"PayoutBankAccount",
"PayoutCreatedData",
"PayoutFailedData",
"PayoutPaidData",
"PayoutVerification",
"Plan",
"PlanChange",
Expand All @@ -225,35 +319,54 @@
"PortalAccess",
"PreviewChange",
"PromoCode",
"QuotaExceededData",
"QuotaThresholdReachedData",
"RemovedPlanFeature",
"RemovedPlanFromGroup",
"ReorderedPlans",
"SeatBalance",
"SeatBalanceListItem",
"SeatEvent",
"SeatsLimitReachedData",
"SeatsUpdatedData",
"SentInvoice",
"SetPlanRegionalPricingParamsFeaturesItem",
"SetPlanRegionalPricingParamsIntroOffersItem",
"SetPlanRegionalPricingParamsPricesItem",
"Subscription",
"SubscriptionActivatedData",
"SubscriptionAddon",
"SubscriptionBalance",
"SubscriptionCanceledData",
"SubscriptionCancellation",
"SubscriptionCancellationRevokedData",
"SubscriptionCancellationScheduledData",
"SubscriptionCreatedData",
"SubscriptionCredits",
"SubscriptionCurrentPeriod",
"SubscriptionDiscount",
"SubscriptionFeaturesItem",
"SubscriptionFeaturesItemUsage",
"SubscriptionPastDueData",
"SubscriptionPlan",
"SubscriptionPlanChangeRevokedData",
"SubscriptionPlanChangeScheduledData",
"SubscriptionPlanChangedData",
"SubscriptionScheduledPlanChange",
"SubscriptionStatus",
"SubscriptionUpdatedData",
"TestClock",
"TestClockBilling",
"Timezone",
"Transaction",
"TransactionRefund",
"TransactionRetry",
"TransactionStatus",
"TrialCheckoutReadyData",
"TrialConvertedData",
"TrialExpiredData",
"TrialStartedData",
"TrialWillEndData",
"UncanceledSubscription",
"UpdateCustomerParamsAddress",
"UpdatePlanFeatureParamsOverage",
Expand All @@ -264,8 +377,19 @@
"UsageEvent",
"UsageQuota",
"UsageQuotaEvent",
"UsageRecordedData",
"WebhookAddonRef",
"WebhookBalance",
"WebhookBankRef",
"WebhookCardInfo",
"WebhookCreditsBalance",
"WebhookEndpoint",
"WebhookEndpointCreated",
"WebhookEvent",
"WebhookEventType",
"WebhookFeatureAccess",
"WebhookPlanRef",
"WebhookSeatSummary",
"WebhookTestResult",
"Webhooks",
"__version__",
Expand Down
4 changes: 1 addition & 3 deletions src/commet/_async_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@ async def _execute(
logger.debug("Response: %d", resp.status_code)

if resp.status_code in _RETRYABLE_STATUS_CODES and attempt <= self._max_retries:
delay = retry_delay_seconds(
resp.status_code, resp.headers.get("retry-after"), attempt
)
delay = retry_delay_seconds(resp.status_code, resp.headers.get("retry-after"), attempt)
if delay is not None:
await self._wait(delay, attempt)
return await self._execute(
Expand Down
4 changes: 1 addition & 3 deletions src/commet/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,7 @@ def _execute(
logger.debug("Response: %d", resp.status_code)

if resp.status_code in _RETRYABLE_STATUS_CODES and attempt <= self._max_retries:
delay = retry_delay_seconds(
resp.status_code, resp.headers.get("retry-after"), attempt
)
delay = retry_delay_seconds(resp.status_code, resp.headers.get("retry-after"), attempt)
if delay is not None:
self._wait(delay, attempt)
return self._execute(
Expand Down
1 change: 1 addition & 0 deletions src/commet/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def retry_delay_seconds(
return None
return min(seconds, _RETRY_AFTER_CAP_SECONDS)


_BASE_URL = "https://commet.co"

API_VERSION = "2026-06-10"
Expand Down
101 changes: 101 additions & 0 deletions src/commet/_webhook_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import annotations

from dataclasses import dataclass


@dataclass
class WebhookPlanRef:
id: str = ""
name: str = ""


@dataclass
class WebhookAddonRef:
id: str = ""
name: str = ""


@dataclass
class WebhookCardInfo:
brand: str = ""
last4: str = ""
expMonth: int = 0
expYear: int = 0


@dataclass
class WebhookBankRef:
bankName: str = ""
last4: str = ""


@dataclass
class WebhookFeatureAccess:
code: str = ""
name: str = ""
type: str = ""
allowed: bool = False
enabled: bool | None = None
current: float | None = None
included: float | None = None
remaining: float | None = None
overageQuantity: float | None = None
overageUnitPrice: float | None = None
unlimited: bool | None = None
overageEnabled: bool | None = None
billedQuantity: float | None = None


@dataclass
class WebhookSeatSummary:
code: str = ""
current: float | None = None
included: float | None = None
remaining: float | None = None
unlimited: bool | None = None


@dataclass
class WebhookCreditsBalance:
planCredits: float = 0.0
purchasedCredits: float = 0.0
totalCredits: float = 0.0


@dataclass
class WebhookBalance:
currentBalance: float = 0.0


_AUX_TYPES = (
WebhookPlanRef,
WebhookAddonRef,
WebhookCardInfo,
WebhookBankRef,
WebhookFeatureAccess,
WebhookSeatSummary,
WebhookCreditsBalance,
WebhookBalance,
)


def _register() -> None:
from .types import _DATACLASS_TYPES

for cls in _AUX_TYPES:
_DATACLASS_TYPES.setdefault(cls.__name__, cls)


_register()


__all__ = [
"WebhookAddonRef",
"WebhookBalance",
"WebhookBankRef",
"WebhookCardInfo",
"WebhookCreditsBalance",
"WebhookFeatureAccess",
"WebhookPlanRef",
"WebhookSeatSummary",
]
Loading