Skip to content

Commit de45c08

Browse files
feat(api): add webhook schemas to SDKs - add parse and parse_unsafe
chore: replace custom webhook signature verification with standardwebhooks
1 parent dc9becd commit de45c08

File tree

126 files changed

+5726
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+5726
-2
lines changed

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 175
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-abe6a4f82f696099fa8ecb1cc44f08979e17d56578ae7ea68b0e9182e21df508.yml
33
openapi_spec_hash: d2ce51592a9a234c6f34a1168a31f91f
4-
config_hash: 739714a3fead0b26ee3a3b7bc51081f6
4+
config_hash: f4b1d2f464e80527f970de61cba0c52f

api.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,3 +820,122 @@ Methods:
820820

821821
- <code title="get /v1/account_activity">client.account_activity.<a href="./src/lithic/resources/account_activity.py">list</a>(\*\*<a href="src/lithic/types/account_activity_list_params.py">params</a>) -> <a href="./src/lithic/types/account_activity_list_response.py">SyncCursorPage[AccountActivityListResponse]</a></code>
822822
- <code title="get /v1/account_activity/{transaction_token}">client.account_activity.<a href="./src/lithic/resources/account_activity.py">retrieve_transaction</a>(transaction_token) -> <a href="./src/lithic/types/account_activity_retrieve_transaction_response.py">AccountActivityRetrieveTransactionResponse</a></code>
823+
824+
# Webhooks
825+
826+
Types:
827+
828+
```python
829+
from lithic.types import (
830+
AccountHolderCreatedWebhookEvent,
831+
AccountHolderUpdatedWebhookEvent,
832+
AccountHolderVerificationWebhookEvent,
833+
AccountHolderDocumentUpdatedWebhookEvent,
834+
AsaRequestWebhookEvent,
835+
TokenizationDecisioningRequestWebhookEvent,
836+
AuthRulesBacktestReportCreatedWebhookEvent,
837+
BalanceUpdatedWebhookEvent,
838+
BookTransferTransactionCreatedWebhookEvent,
839+
BookTransferTransactionUpdatedWebhookEvent,
840+
CardCreatedWebhookEvent,
841+
CardConvertedWebhookEvent,
842+
CardRenewedWebhookEvent,
843+
CardReissuedWebhookEvent,
844+
CardShippedWebhookEvent,
845+
CardTransactionUpdatedWebhookEvent,
846+
CardTransactionEnhancedDataCreatedWebhookEvent,
847+
CardTransactionEnhancedDataUpdatedWebhookEvent,
848+
DigitalWalletTokenizationApprovalRequestWebhookEvent,
849+
DigitalWalletTokenizationResultWebhookEvent,
850+
DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent,
851+
DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent,
852+
DigitalWalletTokenizationUpdatedWebhookEvent,
853+
DisputeUpdatedWebhookEvent,
854+
DisputeEvidenceUploadFailedWebhookEvent,
855+
ExternalBankAccountCreatedWebhookEvent,
856+
ExternalBankAccountUpdatedWebhookEvent,
857+
ExternalPaymentCreatedWebhookEvent,
858+
ExternalPaymentUpdatedWebhookEvent,
859+
FinancialAccountCreatedWebhookEvent,
860+
FinancialAccountUpdatedWebhookEvent,
861+
FundingEventCreatedWebhookEvent,
862+
LoanTapeCreatedWebhookEvent,
863+
LoanTapeUpdatedWebhookEvent,
864+
ManagementOperationCreatedWebhookEvent,
865+
ManagementOperationUpdatedWebhookEvent,
866+
InternalTransactionCreatedWebhookEvent,
867+
InternalTransactionUpdatedWebhookEvent,
868+
NetworkTotalCreatedWebhookEvent,
869+
NetworkTotalUpdatedWebhookEvent,
870+
PaymentTransactionCreatedWebhookEvent,
871+
PaymentTransactionUpdatedWebhookEvent,
872+
SettlementReportUpdatedWebhookEvent,
873+
StatementsCreatedWebhookEvent,
874+
ThreeDSAuthenticationCreatedWebhookEvent,
875+
ThreeDSAuthenticationUpdatedWebhookEvent,
876+
ThreeDSAuthenticationChallengeWebhookEvent,
877+
TokenizationApprovalRequestWebhookEvent,
878+
TokenizationResultWebhookEvent,
879+
TokenizationTwoFactorAuthenticationCodeWebhookEvent,
880+
TokenizationTwoFactorAuthenticationCodeSentWebhookEvent,
881+
TokenizationUpdatedWebhookEvent,
882+
DisputeTransactionCreatedWebhookEvent,
883+
DisputeTransactionUpdatedWebhookEvent,
884+
AccountHolderCreatedWebhookEvent,
885+
AccountHolderUpdatedWebhookEvent,
886+
AccountHolderVerificationWebhookEvent,
887+
AccountHolderDocumentUpdatedWebhookEvent,
888+
AsaRequestWebhookEvent,
889+
TokenizationDecisioningRequestWebhookEvent,
890+
AuthRulesBacktestReportCreatedWebhookEvent,
891+
BalanceUpdatedWebhookEvent,
892+
BookTransferTransactionCreatedWebhookEvent,
893+
BookTransferTransactionUpdatedWebhookEvent,
894+
CardCreatedWebhookEvent,
895+
CardConvertedWebhookEvent,
896+
CardRenewedWebhookEvent,
897+
CardReissuedWebhookEvent,
898+
CardShippedWebhookEvent,
899+
CardTransactionUpdatedWebhookEvent,
900+
CardTransactionEnhancedDataCreatedWebhookEvent,
901+
CardTransactionEnhancedDataUpdatedWebhookEvent,
902+
DigitalWalletTokenizationApprovalRequestWebhookEvent,
903+
DigitalWalletTokenizationResultWebhookEvent,
904+
DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent,
905+
DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent,
906+
DigitalWalletTokenizationUpdatedWebhookEvent,
907+
DisputeUpdatedWebhookEvent,
908+
DisputeEvidenceUploadFailedWebhookEvent,
909+
ExternalBankAccountCreatedWebhookEvent,
910+
ExternalBankAccountUpdatedWebhookEvent,
911+
ExternalPaymentCreatedWebhookEvent,
912+
ExternalPaymentUpdatedWebhookEvent,
913+
FinancialAccountCreatedWebhookEvent,
914+
FinancialAccountUpdatedWebhookEvent,
915+
FundingEventCreatedWebhookEvent,
916+
LoanTapeCreatedWebhookEvent,
917+
LoanTapeUpdatedWebhookEvent,
918+
ManagementOperationCreatedWebhookEvent,
919+
ManagementOperationUpdatedWebhookEvent,
920+
InternalTransactionCreatedWebhookEvent,
921+
InternalTransactionUpdatedWebhookEvent,
922+
NetworkTotalCreatedWebhookEvent,
923+
NetworkTotalUpdatedWebhookEvent,
924+
PaymentTransactionCreatedWebhookEvent,
925+
PaymentTransactionUpdatedWebhookEvent,
926+
SettlementReportUpdatedWebhookEvent,
927+
StatementsCreatedWebhookEvent,
928+
ThreeDSAuthenticationCreatedWebhookEvent,
929+
ThreeDSAuthenticationUpdatedWebhookEvent,
930+
ThreeDSAuthenticationChallengeWebhookEvent,
931+
TokenizationApprovalRequestWebhookEvent,
932+
TokenizationResultWebhookEvent,
933+
TokenizationTwoFactorAuthenticationCodeWebhookEvent,
934+
TokenizationTwoFactorAuthenticationCodeSentWebhookEvent,
935+
TokenizationUpdatedWebhookEvent,
936+
DisputeTransactionCreatedWebhookEvent,
937+
DisputeTransactionUpdatedWebhookEvent,
938+
UnwrapTypedWebhookEvent,
939+
UnwrapUnsafeWebhookEvent,
940+
)
941+
```

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Repository = "https://github.com/lithic-com/lithic-python"
4242

4343
[project.optional-dependencies]
4444
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
45+
webhooks = ["standardwebhooks"]
4546

4647
[tool.rye]
4748
managed = true
@@ -59,6 +60,7 @@ dev-dependencies = [
5960
"importlib-metadata>=6.7.0",
6061
"rich>=13.7.1",
6162
"pytest-xdist>=3.6.1",
63+
"standardwebhooks",
6264
]
6365

6466
[tool.rye.scripts]

requirements-dev.lock

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ async-timeout==5.0.1
2929
attrs==25.4.0
3030
# via aiohttp
3131
# via nox
32+
# via standardwebhooks
3233
backports-asyncio-runner==1.2.0
3334
# via pytest-asyncio
3435
certifi==2025.11.12
@@ -38,6 +39,8 @@ colorlog==6.10.1
3839
# via nox
3940
dependency-groups==1.3.1
4041
# via nox
42+
deprecated==1.3.1
43+
# via standardwebhooks
4144
dirty-equals==0.11
4245
distlib==0.4.0
4346
# via virtualenv
@@ -61,6 +64,7 @@ httpx==0.28.1
6164
# via httpx-aiohttp
6265
# via lithic
6366
# via respx
67+
# via standardwebhooks
6468
httpx-aiohttp==0.1.9
6569
# via lithic
6670
humanize==4.13.0
@@ -112,6 +116,7 @@ pytest==8.4.2
112116
pytest-asyncio==1.2.0
113117
pytest-xdist==3.8.0
114118
python-dateutil==2.9.0.post0
119+
# via standardwebhooks
115120
# via time-machine
116121
respx==0.22.0
117122
rich==14.2.0
@@ -120,12 +125,18 @@ six==1.17.0
120125
# via python-dateutil
121126
sniffio==1.3.1
122127
# via lithic
128+
standardwebhooks==1.0.0
129+
# via lithic
123130
time-machine==2.19.0
124131
tomli==2.3.0
125132
# via dependency-groups
126133
# via mypy
127134
# via nox
128135
# via pytest
136+
types-deprecated==1.3.1.20251101
137+
# via standardwebhooks
138+
types-python-dateutil==2.9.0.20251115
139+
# via standardwebhooks
129140
typing-extensions==4.15.0
130141
# via aiosignal
131142
# via anyio
@@ -143,6 +154,8 @@ typing-inspection==0.4.2
143154
# via pydantic
144155
virtualenv==20.35.4
145156
# via nox
157+
wrapt==2.0.1
158+
# via deprecated
146159
yarl==1.22.0
147160
# via aiohttp
148161
zipp==3.23.0

requirements.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ async-timeout==5.0.1
2626
# via aiohttp
2727
attrs==25.4.0
2828
# via aiohttp
29+
# via standardwebhooks
2930
certifi==2025.11.12
3031
# via httpcore
3132
# via httpx
33+
deprecated==1.3.1
34+
# via standardwebhooks
3235
distro==1.9.0
3336
# via lithic
3437
exceptiongroup==1.3.1
@@ -43,6 +46,7 @@ httpcore==1.0.9
4346
httpx==0.28.1
4447
# via httpx-aiohttp
4548
# via lithic
49+
# via standardwebhooks
4650
httpx-aiohttp==0.1.9
4751
# via lithic
4852
idna==3.11
@@ -59,8 +63,18 @@ pydantic==2.12.5
5963
# via lithic
6064
pydantic-core==2.41.5
6165
# via pydantic
66+
python-dateutil==2.9.0.post0
67+
# via standardwebhooks
68+
six==1.17.0
69+
# via python-dateutil
6270
sniffio==1.3.1
6371
# via lithic
72+
standardwebhooks==1.0.0
73+
# via lithic
74+
types-deprecated==1.3.1.20251101
75+
# via standardwebhooks
76+
types-python-dateutil==2.9.0.20251115
77+
# via standardwebhooks
6478
typing-extensions==4.15.0
6579
# via aiosignal
6680
# via anyio
@@ -72,5 +86,7 @@ typing-extensions==4.15.0
7286
# via typing-inspection
7387
typing-inspection==0.4.2
7488
# via pydantic
89+
wrapt==2.0.1
90+
# via deprecated
7591
yarl==1.22.0
7692
# via aiohttp

src/lithic/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
InternalServerError,
3636
PermissionDeniedError,
3737
UnprocessableEntityError,
38+
APIWebhookValidationError,
3839
APIResponseValidationError,
3940
)
4041
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
@@ -58,6 +59,7 @@
5859
"APITimeoutError",
5960
"APIConnectionError",
6061
"APIResponseValidationError",
62+
"APIWebhookValidationError",
6163
"BadRequestError",
6264
"AuthenticationError",
6365
"PermissionDeniedError",

src/lithic/_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
from .resources.balances import Balances, AsyncBalances
7575
from .resources.disputes import Disputes, AsyncDisputes
7676
from .resources.payments import Payments, AsyncPayments
77+
from .resources.webhooks import Webhooks, AsyncWebhooks
7778
from .resources.cards.cards import Cards, AsyncCards
7879
from .resources.disputes_v2 import DisputesV2, AsyncDisputesV2
7980
from .resources.fraud.fraud import Fraud, AsyncFraud
@@ -392,6 +393,12 @@ def account_activity(self) -> AccountActivity:
392393

393394
return AccountActivity(self)
394395

396+
@cached_property
397+
def webhooks(self) -> Webhooks:
398+
from .resources.webhooks import Webhooks
399+
400+
return Webhooks(self)
401+
395402
@cached_property
396403
def with_raw_response(self) -> LithicWithRawResponse:
397404
return LithicWithRawResponse(self)

src/lithic/_exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def __init__(self, response: httpx.Response, body: object | None, *, message: st
5454
self.status_code = response.status_code
5555

5656

57+
class APIWebhookValidationError(APIError):
58+
pass
59+
60+
5761
class APIStatusError(APIError):
5862
"""Raised when an API response has a status code of 4xx or 5xx."""
5963

src/lithic/resources/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,6 @@
425425
"AsyncAccountActivityWithRawResponse",
426426
"AccountActivityWithStreamingResponse",
427427
"AsyncAccountActivityWithStreamingResponse",
428+
"Webhooks",
429+
"AsyncWebhooks",
428430
]

src/lithic/resources/webhooks.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<<<<<<< HEAD
12
# File generated from our OpenAPI spec by Stainless.
23

34
from __future__ import annotations
@@ -205,3 +206,75 @@ def verify_signature(
205206
return None
206207

207208
raise ValueError("None of the given webhook signatures match the expected signature")
209+
||||||| parent of 49e654d (feat(api): add webhook schemas to SDKs - add parse and parse_unsafe)
210+
=======
211+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
212+
213+
from __future__ import annotations
214+
215+
import json
216+
from typing import Mapping, cast
217+
218+
from .._models import construct_type
219+
from .._resource import SyncAPIResource, AsyncAPIResource
220+
from .._exceptions import LithicError
221+
from ..types.parsed_webhook_event import ParsedWebhookEvent
222+
223+
__all__ = ["Webhooks", "AsyncWebhooks"]
224+
225+
226+
class Webhooks(SyncAPIResource):
227+
def parsed(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> ParsedWebhookEvent:
228+
try:
229+
from standardwebhooks import Webhook
230+
except ImportError as exc:
231+
raise LithicError("You need to install `lithic[webhooks]` to use this method") from exc
232+
233+
if key is None:
234+
key = self._client.webhook_secret
235+
if key is None:
236+
raise ValueError(
237+
"Cannot verify a webhook without a key on either the client's webhook_secret or passed in as an argument"
238+
)
239+
240+
if not isinstance(headers, dict):
241+
headers = dict(headers)
242+
243+
Webhook(key).verify(payload, headers)
244+
245+
return cast(
246+
ParsedWebhookEvent,
247+
construct_type(
248+
type_=ParsedWebhookEvent,
249+
value=json.loads(payload),
250+
),
251+
)
252+
253+
254+
class AsyncWebhooks(AsyncAPIResource):
255+
def parsed(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> ParsedWebhookEvent:
256+
try:
257+
from standardwebhooks import Webhook
258+
except ImportError as exc:
259+
raise LithicError("You need to install `lithic[webhooks]` to use this method") from exc
260+
261+
if key is None:
262+
key = self._client.webhook_secret
263+
if key is None:
264+
raise ValueError(
265+
"Cannot verify a webhook without a key on either the client's webhook_secret or passed in as an argument"
266+
)
267+
268+
if not isinstance(headers, dict):
269+
headers = dict(headers)
270+
271+
Webhook(key).verify(payload, headers)
272+
273+
return cast(
274+
ParsedWebhookEvent,
275+
construct_type(
276+
type_=ParsedWebhookEvent,
277+
value=json.loads(payload),
278+
),
279+
)
280+
>>>>>>> 49e654d (feat(api): add webhook schemas to SDKs - add parse and parse_unsafe)

0 commit comments

Comments
 (0)