hookbase 1.0.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.
Files changed (75) hide show
  1. hookbase-1.0.0/.gitignore +13 -0
  2. hookbase-1.0.0/LICENSE +21 -0
  3. hookbase-1.0.0/PKG-INFO +268 -0
  4. hookbase-1.0.0/README.md +234 -0
  5. hookbase-1.0.0/examples/async_usage.py +36 -0
  6. hookbase-1.0.0/examples/basic_inbound.py +41 -0
  7. hookbase-1.0.0/examples/error_handling.py +52 -0
  8. hookbase-1.0.0/examples/send_webhooks.py +55 -0
  9. hookbase-1.0.0/examples/verify_webhooks.py +66 -0
  10. hookbase-1.0.0/pyproject.toml +76 -0
  11. hookbase-1.0.0/src/hookbase/__init__.py +39 -0
  12. hookbase-1.0.0/src/hookbase/_client.py +309 -0
  13. hookbase-1.0.0/src/hookbase/_constants.py +4 -0
  14. hookbase-1.0.0/src/hookbase/_pagination.py +355 -0
  15. hookbase-1.0.0/src/hookbase/_version.py +1 -0
  16. hookbase-1.0.0/src/hookbase/client.py +270 -0
  17. hookbase-1.0.0/src/hookbase/errors.py +166 -0
  18. hookbase-1.0.0/src/hookbase/models/__init__.py +203 -0
  19. hookbase-1.0.0/src/hookbase/models/_base.py +14 -0
  20. hookbase-1.0.0/src/hookbase/models/analytics.py +22 -0
  21. hookbase-1.0.0/src/hookbase/models/api_keys.py +36 -0
  22. hookbase-1.0.0/src/hookbase/models/applications.py +26 -0
  23. hookbase-1.0.0/src/hookbase/models/common.py +42 -0
  24. hookbase-1.0.0/src/hookbase/models/cron_jobs.py +78 -0
  25. hookbase-1.0.0/src/hookbase/models/deliveries.py +53 -0
  26. hookbase-1.0.0/src/hookbase/models/destinations.py +71 -0
  27. hookbase-1.0.0/src/hookbase/models/dlq.py +60 -0
  28. hookbase-1.0.0/src/hookbase/models/endpoints.py +76 -0
  29. hookbase-1.0.0/src/hookbase/models/event_types.py +37 -0
  30. hookbase-1.0.0/src/hookbase/models/events.py +70 -0
  31. hookbase-1.0.0/src/hookbase/models/filters.py +60 -0
  32. hookbase-1.0.0/src/hookbase/models/messages.py +73 -0
  33. hookbase-1.0.0/src/hookbase/models/organizations.py +32 -0
  34. hookbase-1.0.0/src/hookbase/models/routes.py +97 -0
  35. hookbase-1.0.0/src/hookbase/models/schemas.py +41 -0
  36. hookbase-1.0.0/src/hookbase/models/sources.py +72 -0
  37. hookbase-1.0.0/src/hookbase/models/subscriptions.py +22 -0
  38. hookbase-1.0.0/src/hookbase/models/transforms.py +49 -0
  39. hookbase-1.0.0/src/hookbase/models/tunnels.py +19 -0
  40. hookbase-1.0.0/src/hookbase/py.typed +0 -0
  41. hookbase-1.0.0/src/hookbase/resources/__init__.py +43 -0
  42. hookbase-1.0.0/src/hookbase/resources/_base.py +80 -0
  43. hookbase-1.0.0/src/hookbase/resources/analytics.py +56 -0
  44. hookbase-1.0.0/src/hookbase/resources/api_keys.py +38 -0
  45. hookbase-1.0.0/src/hookbase/resources/applications.py +116 -0
  46. hookbase-1.0.0/src/hookbase/resources/cron_jobs.py +104 -0
  47. hookbase-1.0.0/src/hookbase/resources/deliveries.py +81 -0
  48. hookbase-1.0.0/src/hookbase/resources/destinations.py +138 -0
  49. hookbase-1.0.0/src/hookbase/resources/dlq.py +99 -0
  50. hookbase-1.0.0/src/hookbase/resources/endpoints.py +146 -0
  51. hookbase-1.0.0/src/hookbase/resources/event_types.py +92 -0
  52. hookbase-1.0.0/src/hookbase/resources/events.py +108 -0
  53. hookbase-1.0.0/src/hookbase/resources/filters.py +112 -0
  54. hookbase-1.0.0/src/hookbase/resources/message_log.py +148 -0
  55. hookbase-1.0.0/src/hookbase/resources/messages.py +70 -0
  56. hookbase-1.0.0/src/hookbase/resources/organizations.py +110 -0
  57. hookbase-1.0.0/src/hookbase/resources/routes.py +177 -0
  58. hookbase-1.0.0/src/hookbase/resources/schemas.py +72 -0
  59. hookbase-1.0.0/src/hookbase/resources/sources.py +150 -0
  60. hookbase-1.0.0/src/hookbase/resources/subscriptions.py +114 -0
  61. hookbase-1.0.0/src/hookbase/resources/transforms.py +119 -0
  62. hookbase-1.0.0/src/hookbase/resources/tunnels.py +51 -0
  63. hookbase-1.0.0/src/hookbase/webhook.py +165 -0
  64. hookbase-1.0.0/tests/__init__.py +0 -0
  65. hookbase-1.0.0/tests/conftest.py +68 -0
  66. hookbase-1.0.0/tests/resources/__init__.py +0 -0
  67. hookbase-1.0.0/tests/resources/test_applications.py +77 -0
  68. hookbase-1.0.0/tests/resources/test_destinations.py +72 -0
  69. hookbase-1.0.0/tests/resources/test_dlq.py +88 -0
  70. hookbase-1.0.0/tests/resources/test_messages.py +84 -0
  71. hookbase-1.0.0/tests/resources/test_sources.py +112 -0
  72. hookbase-1.0.0/tests/test_client.py +65 -0
  73. hookbase-1.0.0/tests/test_errors.py +104 -0
  74. hookbase-1.0.0/tests/test_pagination.py +84 -0
  75. hookbase-1.0.0/tests/test_webhook.py +151 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .mypy_cache/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ *.so
12
+ .venv/
13
+ venv/
hookbase-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hookbase
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,268 @@
1
+ Metadata-Version: 2.4
2
+ Name: hookbase
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for the Hookbase webhook management platform
5
+ Project-URL: Homepage, https://hookbase.app
6
+ Project-URL: Documentation, https://docs.hookbase.app/sdk/python
7
+ Project-URL: Repository, https://github.com/hookbase/hookbase-python
8
+ Project-URL: Issues, https://github.com/hookbase/hookbase-python/issues
9
+ Author-email: Hookbase <support@hookbase.app>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: httpx<1.0.0,>=0.25.0
26
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: mypy>=1.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
30
+ Requires-Dist: pytest>=7.0; extra == 'dev'
31
+ Requires-Dist: respx>=0.21; extra == 'dev'
32
+ Requires-Dist: ruff>=0.1; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Hookbase Python SDK
36
+
37
+ The official Python SDK for the [Hookbase](https://hookbase.app) webhook management platform.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install hookbase
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from hookbase import Hookbase
49
+
50
+ client = Hookbase(api_key="whr_your_api_key")
51
+
52
+ # List webhook sources
53
+ sources = client.sources.list()
54
+ for source in sources.data:
55
+ print(f"{source.name} ({source.provider})")
56
+
57
+ # Send an outbound webhook
58
+ result = client.outbound.messages.send(
59
+ "app_123",
60
+ event_type="order.created",
61
+ payload={"orderId": "456", "amount": 99.99},
62
+ )
63
+ print(f"Sent: {result.event_id}")
64
+ ```
65
+
66
+ ## Async Support
67
+
68
+ ```python
69
+ from hookbase import AsyncHookbase
70
+
71
+ async with AsyncHookbase(api_key="whr_...") as client:
72
+ sources = await client.sources.list()
73
+ ```
74
+
75
+ ## API Reference
76
+
77
+ ### Client Initialization
78
+
79
+ ```python
80
+ from hookbase import Hookbase
81
+
82
+ client = Hookbase(
83
+ api_key="whr_...", # Required
84
+ base_url="https://...", # Default: https://api.hookbase.app
85
+ timeout=30.0, # Seconds (default: 30)
86
+ max_retries=3, # Default: 3
87
+ debug=False, # Log requests
88
+ )
89
+ ```
90
+
91
+ ### Inbound Webhooks
92
+
93
+ ```python
94
+ # Sources
95
+ client.sources.list(search="github", provider="github")
96
+ client.sources.create({"name": "GitHub", "slug": "github", "provider": "github"})
97
+ client.sources.get("src_id")
98
+ client.sources.update("src_id", {"name": "Updated"})
99
+ client.sources.delete("src_id")
100
+ client.sources.rotate_secret("src_id")
101
+
102
+ # Destinations
103
+ client.destinations.list()
104
+ client.destinations.create({"name": "Backend", "url": "https://..."})
105
+ client.destinations.test("dst_id")
106
+
107
+ # Routes
108
+ client.routes.create({"name": "Route", "sourceId": "src_id", "destinationId": "dst_id"})
109
+ client.routes.get_circuit_status("route_id")
110
+ client.routes.reset_circuit("route_id")
111
+
112
+ # Events & Deliveries
113
+ client.events.list(source_id="src_id", from_date="2024-01-01")
114
+ client.events.get("evt_id") # Includes payload and deliveries
115
+ client.deliveries.replay("del_id")
116
+ client.deliveries.bulk_replay(["del_1", "del_2"])
117
+
118
+ # Transforms & Filters
119
+ client.transforms.create({"name": "Extract", "transformType": "jsonata", "code": "$.data"})
120
+ client.transforms.test("txf_id", payload={"data": {"key": "value"}})
121
+ client.filters.test([{"field": "$.type", "operator": "eq", "value": "order"}], payload={...})
122
+
123
+ # Schemas
124
+ client.schemas.create({"name": "OrderSchema", "jsonSchema": {"type": "object", ...}})
125
+ ```
126
+
127
+ ### Outbound Webhooks
128
+
129
+ ```python
130
+ # Applications
131
+ client.outbound.applications.create({"name": "Acme", "uid": "cust_123"})
132
+ client.outbound.applications.get_by_external_id("cust_123")
133
+
134
+ # Endpoints
135
+ client.outbound.endpoints.create("app_id", {"url": "https://..."})
136
+ client.outbound.endpoints.rotate_secret("ep_id", grace_period=3600)
137
+
138
+ # Event Types & Subscriptions
139
+ client.outbound.event_types.create({"name": "order.created", "category": "orders"})
140
+ client.outbound.subscriptions.create({"endpointId": "ep_id", "eventTypeId": "et_id"})
141
+ client.outbound.subscriptions.bulk_create("ep_id", ["et_1", "et_2"])
142
+
143
+ # Send Events
144
+ client.outbound.messages.send("app_id", event_type="order.created", payload={...})
145
+
146
+ # Message Log
147
+ client.outbound.message_log.list(application_id="app_id")
148
+ client.outbound.message_log.list_attempts("msg_id")
149
+ client.outbound.message_log.stats()
150
+
151
+ # Dead Letter Queue
152
+ client.outbound.dlq.list()
153
+ client.outbound.dlq.stats()
154
+ client.outbound.dlq.retry("dlq_id")
155
+ client.outbound.dlq.retry_bulk(["dlq_1", "dlq_2"])
156
+ ```
157
+
158
+ ### Admin
159
+
160
+ ```python
161
+ # Organizations
162
+ client.organizations.list()
163
+ client.organizations.list_members("org_id")
164
+ client.organizations.invite("org_id", "user@example.com", "member")
165
+
166
+ # API Keys
167
+ client.api_keys.create({"name": "CI Key", "scopes": ["read", "write"]})
168
+
169
+ # Analytics
170
+ client.analytics.dashboard(range="30d")
171
+
172
+ # Cron Jobs & Tunnels
173
+ client.cron_jobs.list()
174
+ client.tunnels.list()
175
+ ```
176
+
177
+ ### Pagination
178
+
179
+ ```python
180
+ # Offset-based (sources, destinations, routes, etc.)
181
+ page = client.sources.list(page_size=10)
182
+ print(f"Total: {page.total}, Page: {page.page}")
183
+
184
+ # Auto-paginate through all items
185
+ for source in page.auto_paging_iter():
186
+ print(source.name)
187
+
188
+ # Manual pagination
189
+ while page.has_more:
190
+ page = page.next_page()
191
+
192
+ # Cursor-based (applications, endpoints, subscriptions, etc.)
193
+ page = client.outbound.applications.list(limit=50)
194
+ for app in page.auto_paging_iter():
195
+ print(app.name)
196
+ ```
197
+
198
+ ### Webhook Verification
199
+
200
+ ```python
201
+ from hookbase import Webhook, WebhookVerificationError
202
+
203
+ wh = Webhook("whsec_your_secret")
204
+
205
+ try:
206
+ payload = wh.verify(request_body, request_headers)
207
+ print(f"Verified: {payload}")
208
+ except WebhookVerificationError as e:
209
+ print(f"Invalid: {e}")
210
+
211
+ # Generate test headers
212
+ headers = wh.generate_test_headers('{"test": true}')
213
+ ```
214
+
215
+ ### Error Handling
216
+
217
+ ```python
218
+ from hookbase import (
219
+ HookbaseError, # Base error
220
+ APIError, # API returned an error
221
+ AuthenticationError, # 401
222
+ ForbiddenError, # 403
223
+ NotFoundError, # 404
224
+ ValidationError, # 400/422
225
+ ConflictError, # 409
226
+ RateLimitError, # 429 (has retry_after)
227
+ TimeoutError, # Request timed out
228
+ NetworkError, # Connection failed
229
+ WebhookVerificationError,
230
+ )
231
+
232
+ try:
233
+ client.sources.get("src_id")
234
+ except NotFoundError:
235
+ print("Not found")
236
+ except RateLimitError as e:
237
+ print(f"Retry after {e.retry_after}s")
238
+ except APIError as e:
239
+ print(f"{e.status_code}: {e} (request_id={e.request_id})")
240
+ ```
241
+
242
+ ### Pydantic Models
243
+
244
+ All responses are typed Pydantic models with camelCase alias support:
245
+
246
+ ```python
247
+ from hookbase.models import Source, CreateSourceParams
248
+
249
+ # Use Pydantic models for type safety
250
+ params = CreateSourceParams(name="GitHub", provider="github")
251
+ source = client.sources.create(params)
252
+
253
+ # Or use plain dicts
254
+ source = client.sources.create({"name": "GitHub", "provider": "github"})
255
+ ```
256
+
257
+ ## Development
258
+
259
+ ```bash
260
+ pip install -e ".[dev]"
261
+ pytest tests/ -v
262
+ mypy src/hookbase
263
+ ruff check src/ tests/
264
+ ```
265
+
266
+ ## License
267
+
268
+ MIT
@@ -0,0 +1,234 @@
1
+ # Hookbase Python SDK
2
+
3
+ The official Python SDK for the [Hookbase](https://hookbase.app) webhook management platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install hookbase
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from hookbase import Hookbase
15
+
16
+ client = Hookbase(api_key="whr_your_api_key")
17
+
18
+ # List webhook sources
19
+ sources = client.sources.list()
20
+ for source in sources.data:
21
+ print(f"{source.name} ({source.provider})")
22
+
23
+ # Send an outbound webhook
24
+ result = client.outbound.messages.send(
25
+ "app_123",
26
+ event_type="order.created",
27
+ payload={"orderId": "456", "amount": 99.99},
28
+ )
29
+ print(f"Sent: {result.event_id}")
30
+ ```
31
+
32
+ ## Async Support
33
+
34
+ ```python
35
+ from hookbase import AsyncHookbase
36
+
37
+ async with AsyncHookbase(api_key="whr_...") as client:
38
+ sources = await client.sources.list()
39
+ ```
40
+
41
+ ## API Reference
42
+
43
+ ### Client Initialization
44
+
45
+ ```python
46
+ from hookbase import Hookbase
47
+
48
+ client = Hookbase(
49
+ api_key="whr_...", # Required
50
+ base_url="https://...", # Default: https://api.hookbase.app
51
+ timeout=30.0, # Seconds (default: 30)
52
+ max_retries=3, # Default: 3
53
+ debug=False, # Log requests
54
+ )
55
+ ```
56
+
57
+ ### Inbound Webhooks
58
+
59
+ ```python
60
+ # Sources
61
+ client.sources.list(search="github", provider="github")
62
+ client.sources.create({"name": "GitHub", "slug": "github", "provider": "github"})
63
+ client.sources.get("src_id")
64
+ client.sources.update("src_id", {"name": "Updated"})
65
+ client.sources.delete("src_id")
66
+ client.sources.rotate_secret("src_id")
67
+
68
+ # Destinations
69
+ client.destinations.list()
70
+ client.destinations.create({"name": "Backend", "url": "https://..."})
71
+ client.destinations.test("dst_id")
72
+
73
+ # Routes
74
+ client.routes.create({"name": "Route", "sourceId": "src_id", "destinationId": "dst_id"})
75
+ client.routes.get_circuit_status("route_id")
76
+ client.routes.reset_circuit("route_id")
77
+
78
+ # Events & Deliveries
79
+ client.events.list(source_id="src_id", from_date="2024-01-01")
80
+ client.events.get("evt_id") # Includes payload and deliveries
81
+ client.deliveries.replay("del_id")
82
+ client.deliveries.bulk_replay(["del_1", "del_2"])
83
+
84
+ # Transforms & Filters
85
+ client.transforms.create({"name": "Extract", "transformType": "jsonata", "code": "$.data"})
86
+ client.transforms.test("txf_id", payload={"data": {"key": "value"}})
87
+ client.filters.test([{"field": "$.type", "operator": "eq", "value": "order"}], payload={...})
88
+
89
+ # Schemas
90
+ client.schemas.create({"name": "OrderSchema", "jsonSchema": {"type": "object", ...}})
91
+ ```
92
+
93
+ ### Outbound Webhooks
94
+
95
+ ```python
96
+ # Applications
97
+ client.outbound.applications.create({"name": "Acme", "uid": "cust_123"})
98
+ client.outbound.applications.get_by_external_id("cust_123")
99
+
100
+ # Endpoints
101
+ client.outbound.endpoints.create("app_id", {"url": "https://..."})
102
+ client.outbound.endpoints.rotate_secret("ep_id", grace_period=3600)
103
+
104
+ # Event Types & Subscriptions
105
+ client.outbound.event_types.create({"name": "order.created", "category": "orders"})
106
+ client.outbound.subscriptions.create({"endpointId": "ep_id", "eventTypeId": "et_id"})
107
+ client.outbound.subscriptions.bulk_create("ep_id", ["et_1", "et_2"])
108
+
109
+ # Send Events
110
+ client.outbound.messages.send("app_id", event_type="order.created", payload={...})
111
+
112
+ # Message Log
113
+ client.outbound.message_log.list(application_id="app_id")
114
+ client.outbound.message_log.list_attempts("msg_id")
115
+ client.outbound.message_log.stats()
116
+
117
+ # Dead Letter Queue
118
+ client.outbound.dlq.list()
119
+ client.outbound.dlq.stats()
120
+ client.outbound.dlq.retry("dlq_id")
121
+ client.outbound.dlq.retry_bulk(["dlq_1", "dlq_2"])
122
+ ```
123
+
124
+ ### Admin
125
+
126
+ ```python
127
+ # Organizations
128
+ client.organizations.list()
129
+ client.organizations.list_members("org_id")
130
+ client.organizations.invite("org_id", "user@example.com", "member")
131
+
132
+ # API Keys
133
+ client.api_keys.create({"name": "CI Key", "scopes": ["read", "write"]})
134
+
135
+ # Analytics
136
+ client.analytics.dashboard(range="30d")
137
+
138
+ # Cron Jobs & Tunnels
139
+ client.cron_jobs.list()
140
+ client.tunnels.list()
141
+ ```
142
+
143
+ ### Pagination
144
+
145
+ ```python
146
+ # Offset-based (sources, destinations, routes, etc.)
147
+ page = client.sources.list(page_size=10)
148
+ print(f"Total: {page.total}, Page: {page.page}")
149
+
150
+ # Auto-paginate through all items
151
+ for source in page.auto_paging_iter():
152
+ print(source.name)
153
+
154
+ # Manual pagination
155
+ while page.has_more:
156
+ page = page.next_page()
157
+
158
+ # Cursor-based (applications, endpoints, subscriptions, etc.)
159
+ page = client.outbound.applications.list(limit=50)
160
+ for app in page.auto_paging_iter():
161
+ print(app.name)
162
+ ```
163
+
164
+ ### Webhook Verification
165
+
166
+ ```python
167
+ from hookbase import Webhook, WebhookVerificationError
168
+
169
+ wh = Webhook("whsec_your_secret")
170
+
171
+ try:
172
+ payload = wh.verify(request_body, request_headers)
173
+ print(f"Verified: {payload}")
174
+ except WebhookVerificationError as e:
175
+ print(f"Invalid: {e}")
176
+
177
+ # Generate test headers
178
+ headers = wh.generate_test_headers('{"test": true}')
179
+ ```
180
+
181
+ ### Error Handling
182
+
183
+ ```python
184
+ from hookbase import (
185
+ HookbaseError, # Base error
186
+ APIError, # API returned an error
187
+ AuthenticationError, # 401
188
+ ForbiddenError, # 403
189
+ NotFoundError, # 404
190
+ ValidationError, # 400/422
191
+ ConflictError, # 409
192
+ RateLimitError, # 429 (has retry_after)
193
+ TimeoutError, # Request timed out
194
+ NetworkError, # Connection failed
195
+ WebhookVerificationError,
196
+ )
197
+
198
+ try:
199
+ client.sources.get("src_id")
200
+ except NotFoundError:
201
+ print("Not found")
202
+ except RateLimitError as e:
203
+ print(f"Retry after {e.retry_after}s")
204
+ except APIError as e:
205
+ print(f"{e.status_code}: {e} (request_id={e.request_id})")
206
+ ```
207
+
208
+ ### Pydantic Models
209
+
210
+ All responses are typed Pydantic models with camelCase alias support:
211
+
212
+ ```python
213
+ from hookbase.models import Source, CreateSourceParams
214
+
215
+ # Use Pydantic models for type safety
216
+ params = CreateSourceParams(name="GitHub", provider="github")
217
+ source = client.sources.create(params)
218
+
219
+ # Or use plain dicts
220
+ source = client.sources.create({"name": "GitHub", "provider": "github"})
221
+ ```
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ pip install -e ".[dev]"
227
+ pytest tests/ -v
228
+ mypy src/hookbase
229
+ ruff check src/ tests/
230
+ ```
231
+
232
+ ## License
233
+
234
+ MIT
@@ -0,0 +1,36 @@
1
+ """Async usage with AsyncHookbase."""
2
+
3
+ import asyncio
4
+
5
+ from hookbase import AsyncHookbase
6
+
7
+
8
+ async def main():
9
+ async with AsyncHookbase(api_key="whr_your_api_key") as client:
10
+ # List sources
11
+ sources = await client.sources.list()
12
+ print(f"Found {sources.total} sources")
13
+
14
+ for src in sources.data:
15
+ print(f" {src.name} ({src.provider})")
16
+
17
+ # Send an outbound webhook
18
+ result = await client.outbound.messages.send(
19
+ "app_123",
20
+ event_type="invoice.paid",
21
+ payload={"invoiceId": "inv_789", "amount": 49.99},
22
+ )
23
+ print(f"Sent event: {result.event_id}")
24
+
25
+ # Check DLQ
26
+ dlq_stats = await client.outbound.dlq.stats()
27
+ print(f"DLQ: {dlq_stats.total} messages")
28
+
29
+ # Paginate through all applications
30
+ page = await client.outbound.applications.list(limit=10)
31
+ async for app in page.auto_paging_iter():
32
+ print(f" App: {app.name} (uid={app.uid})")
33
+
34
+
35
+ if __name__ == "__main__":
36
+ asyncio.run(main())
@@ -0,0 +1,41 @@
1
+ """Basic inbound webhook setup: create source -> destination -> route."""
2
+
3
+ from hookbase import Hookbase
4
+
5
+ client = Hookbase(api_key="whr_your_api_key")
6
+
7
+ # Create a source to receive GitHub webhooks
8
+ source = client.sources.create({
9
+ "name": "GitHub",
10
+ "slug": "github",
11
+ "provider": "github",
12
+ "verifySignature": True,
13
+ })
14
+ print(f"Source created: {source.id}")
15
+ print(f"Ingest URL: {source.ingest_url}")
16
+ print(f"Signing secret: {source.signing_secret}")
17
+
18
+ # Create a destination to forward webhooks to
19
+ dest = client.destinations.create({
20
+ "name": "My Backend",
21
+ "slug": "backend",
22
+ "url": "https://api.example.com/webhooks",
23
+ "method": "POST",
24
+ "retryCount": 3,
25
+ })
26
+ print(f"Destination created: {dest.id}")
27
+
28
+ # Create a route connecting source to destination
29
+ route = client.routes.create({
30
+ "name": "GitHub to Backend",
31
+ "sourceId": source.id,
32
+ "destinationId": dest.id,
33
+ })
34
+ print(f"Route created: {route.id}")
35
+
36
+ # List recent events
37
+ events = client.events.list(source_id=source.id, limit=10)
38
+ for event in events.data:
39
+ print(f" Event {event.id}: {event.event_type} ({event.status})")
40
+
41
+ client.close()
@@ -0,0 +1,52 @@
1
+ """Error handling patterns."""
2
+
3
+ from hookbase import (
4
+ Hookbase,
5
+ AuthenticationError,
6
+ ForbiddenError,
7
+ NotFoundError,
8
+ RateLimitError,
9
+ ValidationError,
10
+ APIError,
11
+ TimeoutError,
12
+ NetworkError,
13
+ )
14
+
15
+ client = Hookbase(api_key="whr_your_api_key")
16
+
17
+ # --- Handling specific errors ---
18
+ try:
19
+ source = client.sources.get("src_nonexistent")
20
+ except NotFoundError:
21
+ print("Source not found")
22
+ except AuthenticationError:
23
+ print("Invalid API key - check your credentials")
24
+ except ForbiddenError as e:
25
+ print(f"Access denied: {e}")
26
+ except RateLimitError as e:
27
+ print(f"Rate limited - retry after {e.retry_after}s")
28
+ except ValidationError as e:
29
+ print(f"Validation failed: {e}")
30
+ if e.validation_errors:
31
+ for field, errors in e.validation_errors.items():
32
+ print(f" {field}: {', '.join(errors)}")
33
+
34
+ # --- Catching all API errors ---
35
+ try:
36
+ client.sources.create({"name": ""})
37
+ except APIError as e:
38
+ print(f"API error {e.status_code}: {e}")
39
+ print(f" Code: {e.code}")
40
+ print(f" Request ID: {e.request_id}")
41
+
42
+ # --- Network / timeout errors ---
43
+ try:
44
+ client.sources.list()
45
+ except TimeoutError:
46
+ print("Request timed out")
47
+ except NetworkError as e:
48
+ print(f"Network error: {e}")
49
+ if e.cause:
50
+ print(f" Caused by: {e.cause}")
51
+
52
+ client.close()