wazzapi 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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
wazzapi-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: wazzapi
3
+ Version: 0.1.0
4
+ Summary: Typed Python SDK for WazzAPI
5
+ Project-URL: API, https://api.wazzapi.com
6
+ Keywords: api,sdk,wazzapi,webhook,whatsapp
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx<1.0,>=0.28
20
+ Requires-Dist: pydantic<3.0,>=2.11
21
+ Description-Content-Type: text/markdown
22
+
23
+ # WazzAPI Python SDK
24
+
25
+ Official Python SDK for WazzAPI.
26
+
27
+ Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
28
+
29
+ ## What you can do with this SDK
30
+
31
+ - send direct WhatsApp messages
32
+ - list, create, update, and delete contacts
33
+ - manage message templates and preview rendered content
34
+ - verify and parse incoming WazzAPI webhooks
35
+ - work with typed request and response models
36
+
37
+ ## Requirements
38
+
39
+ - Python 3.10+
40
+ - a WazzAPI account
41
+ - a WazzAPI API key
42
+
43
+ ## Install
44
+
45
+ From PyPI:
46
+
47
+ ```bash
48
+ pip install wazzapi-py
49
+ ```
50
+
51
+ With uv:
52
+
53
+ ```bash
54
+ uv add wazzapi-py
55
+ ```
56
+
57
+ Need a WazzAPI account first?
58
+
59
+ Sign up at `https://app.wazzapi.com` using your Google organization account. On first sign-in, WazzAPI creates a new workspace for you automatically.
60
+
61
+ ## Configuration
62
+
63
+ For most integrations, you only need your API key:
64
+
65
+ - `WAZZAPI_API_KEY`
66
+
67
+ If you plan to receive webhooks, also configure:
68
+
69
+ - `WAZZAPI_WEBHOOK_SECRET`
70
+
71
+ The SDK uses `https://api.wazzapi.com` by default, so you do not need to set a base URL.
72
+
73
+ ## Quick start
74
+
75
+ ### Send a message
76
+
77
+ ```python
78
+ from wazzapi import SendMessageRequest, WazzapiClient
79
+
80
+ with WazzapiClient(api_key="your-api-key") as client:
81
+ response = client.messages.send(
82
+ SendMessageRequest(
83
+ phone_number="+6281234567890",
84
+ whatsapp_account_id="your-whatsapp-account-id",
85
+ content="Hello from WazzAPI!",
86
+ )
87
+ )
88
+
89
+ print(response.model_dump())
90
+ ```
91
+
92
+ ## Error handling
93
+
94
+ When the API returns a non-success response, the SDK raises `WazzapiAPIError`.
95
+
96
+ ```python
97
+ from wazzapi import WazzapiAPIError, WazzapiClient
98
+
99
+ try:
100
+ with WazzapiClient(api_key="your-api-key") as client:
101
+ client.messages.get("missing-message-id")
102
+ except WazzapiAPIError as exc:
103
+ print(exc.status_code)
104
+ print(exc.message)
105
+ ```
106
+
107
+ ## More usage examples
108
+
109
+ ### List contacts
110
+
111
+ ```python
112
+ from wazzapi import WazzapiClient
113
+
114
+ with WazzapiClient(api_key="your-api-key") as client:
115
+ response = client.contacts.list(limit=20, search="alice")
116
+
117
+ for contact in response.contacts:
118
+ print(contact.model_dump())
119
+ ```
120
+
121
+ ### Create a template
122
+
123
+ ```python
124
+ from wazzapi import TemplateCreateRequest, WazzapiClient
125
+
126
+ with WazzapiClient(api_key="your-api-key") as client:
127
+ template = client.templates.create(
128
+ TemplateCreateRequest(
129
+ name="welcome-message",
130
+ category="marketing",
131
+ content="Hi {{name}}, welcome to WazzAPI!",
132
+ )
133
+ )
134
+
135
+ print(template.model_dump())
136
+ ```
137
+
138
+ ### Preview a template
139
+
140
+ ```python
141
+ from wazzapi import TemplatePreviewRequest, WazzapiClient
142
+
143
+ with WazzapiClient(api_key="your-api-key") as client:
144
+ preview = client.templates.preview(
145
+ TemplatePreviewRequest(
146
+ content="Hi {{name}}, your code is {{code}}.",
147
+ custom_variables={"name": "Alice", "code": "WZ-1234"},
148
+ )
149
+ )
150
+
151
+ print(preview.model_dump())
152
+ ```
153
+
154
+ ## Webhook verification
155
+
156
+ Use `WebhookHandler` to verify the raw request body against `X-Wazzapi-Signature` before parsing JSON.
157
+
158
+ ```python
159
+ from wazzapi import WebhookHandler
160
+
161
+ handler = WebhookHandler("your-webhook-secret")
162
+ webhook = handler.verify_and_parse(raw_body, request.headers)
163
+
164
+ print(webhook.event_type)
165
+ print(webhook.data.model_dump())
166
+ ```
167
+
168
+ WazzAPI webhook headers:
169
+
170
+ - `X-Wazzapi-Signature`
171
+ - `X-Wazzapi-Event`
172
+ - `X-Wazzapi-Event-ID`
173
+
174
+ Supported webhook event families:
175
+
176
+ - message events: `message.received`, `message.sent`, `message.delivered`, `message.read`, `message.failed`
177
+ - device events: `device.connected`, `device.disconnected`
178
+
179
+ ## Examples
180
+
181
+ Ready-to-run examples live in `examples/`:
182
+
183
+ - `examples/list_contacts.py`
184
+ - `examples/send_message.py`
185
+ - `examples/create_template.py`
186
+ - `examples/preview_template.py`
187
+ - `examples/verify_webhook.py`
188
+
189
+ Run them with:
190
+
191
+ ```bash
192
+ uv run python examples/list_contacts.py
193
+ uv run python examples/send_message.py
194
+ uv run python examples/create_template.py
195
+ uv run python examples/preview_template.py
196
+ uv run python examples/verify_webhook.py
197
+ ```
198
+
199
+ ## Release automation
200
+
201
+ This repository includes a GitHub Actions workflow that publishes to PyPI automatically when a tag matching `v*` is pushed.
@@ -0,0 +1,179 @@
1
+ # WazzAPI Python SDK
2
+
3
+ Official Python SDK for WazzAPI.
4
+
5
+ Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
6
+
7
+ ## What you can do with this SDK
8
+
9
+ - send direct WhatsApp messages
10
+ - list, create, update, and delete contacts
11
+ - manage message templates and preview rendered content
12
+ - verify and parse incoming WazzAPI webhooks
13
+ - work with typed request and response models
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.10+
18
+ - a WazzAPI account
19
+ - a WazzAPI API key
20
+
21
+ ## Install
22
+
23
+ From PyPI:
24
+
25
+ ```bash
26
+ pip install wazzapi-py
27
+ ```
28
+
29
+ With uv:
30
+
31
+ ```bash
32
+ uv add wazzapi-py
33
+ ```
34
+
35
+ Need a WazzAPI account first?
36
+
37
+ Sign up at `https://app.wazzapi.com` using your Google organization account. On first sign-in, WazzAPI creates a new workspace for you automatically.
38
+
39
+ ## Configuration
40
+
41
+ For most integrations, you only need your API key:
42
+
43
+ - `WAZZAPI_API_KEY`
44
+
45
+ If you plan to receive webhooks, also configure:
46
+
47
+ - `WAZZAPI_WEBHOOK_SECRET`
48
+
49
+ The SDK uses `https://api.wazzapi.com` by default, so you do not need to set a base URL.
50
+
51
+ ## Quick start
52
+
53
+ ### Send a message
54
+
55
+ ```python
56
+ from wazzapi import SendMessageRequest, WazzapiClient
57
+
58
+ with WazzapiClient(api_key="your-api-key") as client:
59
+ response = client.messages.send(
60
+ SendMessageRequest(
61
+ phone_number="+6281234567890",
62
+ whatsapp_account_id="your-whatsapp-account-id",
63
+ content="Hello from WazzAPI!",
64
+ )
65
+ )
66
+
67
+ print(response.model_dump())
68
+ ```
69
+
70
+ ## Error handling
71
+
72
+ When the API returns a non-success response, the SDK raises `WazzapiAPIError`.
73
+
74
+ ```python
75
+ from wazzapi import WazzapiAPIError, WazzapiClient
76
+
77
+ try:
78
+ with WazzapiClient(api_key="your-api-key") as client:
79
+ client.messages.get("missing-message-id")
80
+ except WazzapiAPIError as exc:
81
+ print(exc.status_code)
82
+ print(exc.message)
83
+ ```
84
+
85
+ ## More usage examples
86
+
87
+ ### List contacts
88
+
89
+ ```python
90
+ from wazzapi import WazzapiClient
91
+
92
+ with WazzapiClient(api_key="your-api-key") as client:
93
+ response = client.contacts.list(limit=20, search="alice")
94
+
95
+ for contact in response.contacts:
96
+ print(contact.model_dump())
97
+ ```
98
+
99
+ ### Create a template
100
+
101
+ ```python
102
+ from wazzapi import TemplateCreateRequest, WazzapiClient
103
+
104
+ with WazzapiClient(api_key="your-api-key") as client:
105
+ template = client.templates.create(
106
+ TemplateCreateRequest(
107
+ name="welcome-message",
108
+ category="marketing",
109
+ content="Hi {{name}}, welcome to WazzAPI!",
110
+ )
111
+ )
112
+
113
+ print(template.model_dump())
114
+ ```
115
+
116
+ ### Preview a template
117
+
118
+ ```python
119
+ from wazzapi import TemplatePreviewRequest, WazzapiClient
120
+
121
+ with WazzapiClient(api_key="your-api-key") as client:
122
+ preview = client.templates.preview(
123
+ TemplatePreviewRequest(
124
+ content="Hi {{name}}, your code is {{code}}.",
125
+ custom_variables={"name": "Alice", "code": "WZ-1234"},
126
+ )
127
+ )
128
+
129
+ print(preview.model_dump())
130
+ ```
131
+
132
+ ## Webhook verification
133
+
134
+ Use `WebhookHandler` to verify the raw request body against `X-Wazzapi-Signature` before parsing JSON.
135
+
136
+ ```python
137
+ from wazzapi import WebhookHandler
138
+
139
+ handler = WebhookHandler("your-webhook-secret")
140
+ webhook = handler.verify_and_parse(raw_body, request.headers)
141
+
142
+ print(webhook.event_type)
143
+ print(webhook.data.model_dump())
144
+ ```
145
+
146
+ WazzAPI webhook headers:
147
+
148
+ - `X-Wazzapi-Signature`
149
+ - `X-Wazzapi-Event`
150
+ - `X-Wazzapi-Event-ID`
151
+
152
+ Supported webhook event families:
153
+
154
+ - message events: `message.received`, `message.sent`, `message.delivered`, `message.read`, `message.failed`
155
+ - device events: `device.connected`, `device.disconnected`
156
+
157
+ ## Examples
158
+
159
+ Ready-to-run examples live in `examples/`:
160
+
161
+ - `examples/list_contacts.py`
162
+ - `examples/send_message.py`
163
+ - `examples/create_template.py`
164
+ - `examples/preview_template.py`
165
+ - `examples/verify_webhook.py`
166
+
167
+ Run them with:
168
+
169
+ ```bash
170
+ uv run python examples/list_contacts.py
171
+ uv run python examples/send_message.py
172
+ uv run python examples/create_template.py
173
+ uv run python examples/preview_template.py
174
+ uv run python examples/verify_webhook.py
175
+ ```
176
+
177
+ ## Release automation
178
+
179
+ This repository includes a GitHub Actions workflow that publishes to PyPI automatically when a tag matching `v*` is pushed.
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from wazzapi import TemplateCreateRequest, WazzapiClient
6
+
7
+ api_key = os.environ["WAZZAPI_API_KEY"]
8
+
9
+ with WazzapiClient(api_key=api_key) as client:
10
+ response = client.templates.create(
11
+ TemplateCreateRequest(
12
+ name="welcome-message",
13
+ category="marketing",
14
+ content="Hi {{name}}, welcome to WazzAPI!",
15
+ )
16
+ )
17
+
18
+ print(response.model_dump())
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from wazzapi import WazzapiClient
6
+
7
+ api_key = os.environ["WAZZAPI_API_KEY"]
8
+
9
+ with WazzapiClient(api_key=api_key) as client:
10
+ response = client.contacts.list(limit=20, search="alice")
11
+
12
+ for contact in response.contacts:
13
+ print(contact.model_dump())
14
+
15
+ print({"total": response.total, "limit": response.limit, "offset": response.offset})
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from wazzapi import TemplatePreviewRequest, WazzapiClient
6
+
7
+ api_key = os.environ["WAZZAPI_API_KEY"]
8
+
9
+ with WazzapiClient(api_key=api_key) as client:
10
+ preview = client.templates.preview(
11
+ TemplatePreviewRequest(
12
+ content="Hi {{name}}, your code is {{code}}.",
13
+ custom_variables={"name": "Alice", "code": "WZ-1234"},
14
+ )
15
+ )
16
+
17
+ print(preview.model_dump())
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from wazzapi import SendMessageRequest, WazzapiClient
6
+
7
+ api_key = os.environ["WAZZAPI_API_KEY"]
8
+
9
+ with WazzapiClient(api_key=api_key) as client:
10
+ response = client.messages.send(
11
+ SendMessageRequest(
12
+ phone_number="+6281234567890",
13
+ whatsapp_account_id="your-whatsapp-account-id",
14
+ content="Hello from the WazzAPI Python SDK",
15
+ )
16
+ )
17
+
18
+ print(response.model_dump())
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+
6
+ from wazzapi import WebhookHandler
7
+
8
+ secret = os.environ["WAZZAPI_WEBHOOK_SECRET"]
9
+ handler = WebhookHandler(secret)
10
+
11
+ raw_body = json.dumps(
12
+ {
13
+ "id": "97e1d724-f38d-46f6-bc4c-d9e0f4cf9285",
14
+ "event_type": "message.received",
15
+ "timestamp": "2026-04-26T09:15:30Z",
16
+ "organization_id": "0f1d6e38-d6bc-49be-9c39-1fcf2f946d7e",
17
+ "webhook_id": "a17d6351-d8f6-4cd8-ae0e-fce090afdb8f",
18
+ "data": {
19
+ "message_id": "f4fd147d-e2a8-4d52-9eb5-98a72f5b90ab",
20
+ "whatsapp_message_id": "wamid.123",
21
+ "phone_number": "6281234567890",
22
+ "account_name": "Support Device",
23
+ "status": "delivered",
24
+ "direction": "inbound",
25
+ "message_type": "text",
26
+ "failure_reason": None,
27
+ "reason": None,
28
+ "sent_at": None,
29
+ "delivered_at": "2026-04-26T09:15:29Z",
30
+ "read_at": None,
31
+ "failed_at": None,
32
+ "whatsapp_account_id": "c053d8ef-6c19-4ecb-9cc5-a4a64be79d92",
33
+ "campaign_id": None,
34
+ "batch_id": None,
35
+ },
36
+ },
37
+ separators=(",", ":"),
38
+ )
39
+
40
+ headers = {
41
+ "X-Wazzapi-Signature": handler.generate_signature(raw_body),
42
+ "X-Wazzapi-Event": "message.received",
43
+ "X-Wazzapi-Event-ID": "2e0b76fe-f5b4-4e50-80cb-ec7cce2c8fd5",
44
+ }
45
+
46
+ webhook = handler.verify_and_parse(raw_body, headers)
47
+ print(webhook.event_type)
48
+ print(webhook.data.model_dump())
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "wazzapi"
3
+ version = "0.1.0"
4
+ description = "Typed Python SDK for WazzAPI"
5
+ readme = { file = "README.md", content-type = "text/markdown" }
6
+ requires-python = ">=3.10"
7
+ keywords = ["api", "sdk", "wazzapi", "webhook", "whatsapp"]
8
+ classifiers = [
9
+ "Development Status :: 4 - Beta",
10
+ "Intended Audience :: Developers",
11
+ "Operating System :: OS Independent",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3 :: Only",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "httpx>=0.28,<1.0",
23
+ "pydantic>=2.11,<3.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ API = "https://api.wazzapi.com"
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "pytest>=8.3,<9.0",
32
+ "twine>=6.1,<7.0",
33
+ ]
34
+
35
+ [build-system]
36
+ requires = ["hatchling>=1.27.0"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["wazzapi"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = [
44
+ "/README.md",
45
+ "/examples",
46
+ "/pyproject.toml",
47
+ "/tests",
48
+ "/wazzapi",
49
+ ]
50
+
51
+ [tool.pytest]
52
+ python_files = ["test_*.py"]
53
+ testpaths = ["tests"]
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from wazzapi import (
4
+ SendMessageRequest,
5
+ WazzapiClient,
6
+ WebhookHandler,
7
+ __version__,
8
+ generate_webhook_signature,
9
+ )
10
+
11
+
12
+ def main() -> None:
13
+ assert __version__ == "0.1.0"
14
+
15
+ client = WazzapiClient(api_key="smoke-test-token")
16
+ try:
17
+ assert str(client.http.base_url) == "https://api.wazzapi.com"
18
+ assert client.http.headers["Authorization"] == "Bearer smoke-test-token"
19
+ finally:
20
+ client.close()
21
+
22
+ request = SendMessageRequest(
23
+ phone_number="+6281234567890",
24
+ whatsapp_account_id="wa_123",
25
+ content="smoke test",
26
+ )
27
+ assert request.phone_number == "+6281234567890"
28
+ assert request.content == "smoke test"
29
+
30
+ handler = WebhookHandler("secret")
31
+ signature = generate_webhook_signature(b"{}", "secret")
32
+ assert handler.verify_signature(b"{}", signature)
33
+
34
+ print("smoke-ok")
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main()
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import httpx
6
+ import pytest
7
+
8
+ from wazzapi import WazzapiAPIError, WazzapiClient
9
+ from wazzapi.models import ContactCreateRequest
10
+
11
+
12
+ def test_client_formats_bearer_auth_header() -> None:
13
+ client = WazzapiClient(base_url="https://api.example.com", api_key="plain-token")
14
+ try:
15
+ assert client.http.headers["Authorization"] == "Bearer plain-token"
16
+ assert client.http.headers["Accept"] == "application/json"
17
+ assert client.http.headers["Content-Type"] == "application/json"
18
+ finally:
19
+ client.close()
20
+
21
+
22
+ def test_client_uses_default_wazzapi_base_url() -> None:
23
+ client = WazzapiClient(api_key="plain-token")
24
+ try:
25
+ assert str(client.http.base_url) == "https://api.wazzapi.com"
26
+ finally:
27
+ client.close()
28
+
29
+
30
+ def test_client_preserves_existing_bearer_prefix() -> None:
31
+ client = WazzapiClient(base_url="https://api.example.com", api_key="Bearer already-set")
32
+ try:
33
+ assert client.http.headers["Authorization"] == "Bearer already-set"
34
+ finally:
35
+ client.close()
36
+
37
+
38
+ def test_client_serializes_models_and_filters_none_query_params() -> None:
39
+ seen: dict[str, object] = {}
40
+
41
+ def handler(request: httpx.Request) -> httpx.Response:
42
+ seen["query"] = dict(request.url.params)
43
+ seen["body"] = json.loads(request.content.decode("utf-8"))
44
+ return httpx.Response(
45
+ 201,
46
+ json={
47
+ "id": "contact_1",
48
+ "phone_number": "+6281234567890",
49
+ "source": "manual",
50
+ "is_opted_out": False,
51
+ "tags": [],
52
+ "created_at": "2026-05-01T00:00:00Z",
53
+ "updated_at": "2026-05-01T00:00:00Z",
54
+ },
55
+ )
56
+
57
+ transport = httpx.MockTransport(handler)
58
+ with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
59
+ response = client._request(
60
+ "POST",
61
+ "/api/v1/contacts",
62
+ params={"limit": 10, "search": None},
63
+ json_body=ContactCreateRequest(phone_number="+6281234567890", name=None),
64
+ response_model=None,
65
+ )
66
+
67
+ assert seen["query"] == {"limit": "10"}
68
+ assert seen["body"] == {"phone_number": "+6281234567890"}
69
+ assert response["id"] == "contact_1"
70
+
71
+
72
+ @pytest.mark.parametrize(
73
+ ("payload", "expected_message"),
74
+ [
75
+ ({"detail": "Message not found"}, "Message not found"),
76
+ ({"detail": [{"msg": "Invalid input"}]}, "Invalid input"),
77
+ ({"message": "Rate limited"}, "Rate limited"),
78
+ ],
79
+ )
80
+ def test_client_raises_api_error_with_parsed_message(payload: dict[str, object], expected_message: str) -> None:
81
+ def handler(_: httpx.Request) -> httpx.Response:
82
+ return httpx.Response(400, json=payload)
83
+
84
+ transport = httpx.MockTransport(handler)
85
+ with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
86
+ with pytest.raises(WazzapiAPIError) as exc_info:
87
+ client._request("GET", "/api/v1/test")
88
+
89
+ assert exc_info.value.message == expected_message
90
+ assert exc_info.value.status_code == 400