sendbee-api 1.7.2__tar.gz → 1.8.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.
- {sendbee_api-1.7.2/sendbee_api.egg-info → sendbee_api-1.8.0}/PKG-INFO +79 -3
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/README.md +78 -2
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/pyproject.toml +1 -1
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/client.py +2 -1
- sendbee_api-1.8.0/sendbee_api/webhooks/__init__.py +0 -0
- sendbee_api-1.8.0/sendbee_api/webhooks/client.py +16 -0
- sendbee_api-1.8.0/sendbee_api/webhooks/models.py +22 -0
- sendbee_api-1.8.0/sendbee_api/webhooks/query_params.py +9 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0/sendbee_api.egg-info}/PKG-INFO +79 -3
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api.egg-info/SOURCES.txt +4 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_endpoints_smoke.py +39 -1
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/LICENSE +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/auth.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/automation/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/automation/client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/automation/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/automation/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/bind.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/constants.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/contacts/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/contacts/client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/contacts/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/contacts/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/conversations/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/conversations/client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/conversations/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/conversations/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/debug.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/exceptions.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/fields.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/formatter.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/rate_limit/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/rate_limit/client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/rate_limit/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/rate_limit/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/response.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/teams/__init__.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/teams/client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/teams/models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api/teams/query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api.egg-info/dependency_links.txt +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api.egg-info/requires.txt +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/sendbee_api.egg-info/top_level.txt +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/setup.cfg +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_auth.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_bind_request.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_client.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_fields.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_formatter.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_models.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_query_params.py +0 -0
- {sendbee_api-1.7.2 → sendbee_api-1.8.0}/tests/test_response.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sendbee_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Python client SDK for Sendbee Public API
|
|
5
5
|
Author-email: Sendbee ltd <info@sendbee.io>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,10 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
33
33
|
Requires-Dist: responses>=0.23; extra == "dev"
|
|
34
34
|
Dynamic: license-file
|
|
35
35
|
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="docs/images/number-logo-tm.png" alt="AI Number">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
36
40
|
# AI Number Python API Client
|
|
37
41
|
|
|
38
42
|
[](https://badge.fury.io/py/sendbee-api)
|
|
@@ -80,6 +84,10 @@ Dynamic: license-file
|
|
|
80
84
|
- [Managing chatbot (automated responses) status settings](#bot-on-off)
|
|
81
85
|
- [Get chatbot (automated responses) status](#bot-status)
|
|
82
86
|
|
|
87
|
+
#### Webhooks
|
|
88
|
+
|
|
89
|
+
- [Subscribe to webhook](#subscribe-webhook)
|
|
90
|
+
|
|
83
91
|
#### Mics
|
|
84
92
|
|
|
85
93
|
- [Pagination](#pagination)
|
|
@@ -557,6 +565,59 @@ response.chatbot_active # True/False
|
|
|
557
565
|
|
|
558
566
|
```
|
|
559
567
|
|
|
568
|
+
## Webhooks
|
|
569
|
+
|
|
570
|
+
### <a href='#subscribe-webhook'>Subscribe to webhook</a>
|
|
571
|
+
|
|
572
|
+
Register a URL of yours and subscribe it to a webhook event, so we start sending
|
|
573
|
+
you requests when that event happens. This does the same thing as activating a
|
|
574
|
+
webhook URL in the Sendbee Dashboard.
|
|
575
|
+
|
|
576
|
+
```python
|
|
577
|
+
webhook = api.subscribe_webhook(
|
|
578
|
+
url='https://example.com/sendbee/webhook',
|
|
579
|
+
event='message.received'
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
webhook.id # UUID of the webhook endpoint
|
|
583
|
+
webhook.url # the URL you registered
|
|
584
|
+
webhook.active # True/False
|
|
585
|
+
webhook.selected_webhooks # list of all events this URL is subscribed to
|
|
586
|
+
webhook.secret_key # key used to sign requests we send to your URL
|
|
587
|
+
webhook.created_at
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Both `url` and `event` are required. Calling it again for the same `url` is safe -
|
|
591
|
+
it will not create a duplicate, it just adds the new event to that URL (and
|
|
592
|
+
re-activates it if it was deactivated), so subscribe one event per call:
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
for event in ('message.received', 'message.sent'):
|
|
596
|
+
api.subscribe_webhook(url='https://example.com/sendbee/webhook', event=event)
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Available events:
|
|
600
|
+
|
|
601
|
+
| Event | Description |
|
|
602
|
+
| --- | --- |
|
|
603
|
+
| `contact.created` | A new contact was created |
|
|
604
|
+
| `contact.subscription_created` | A contact subscribed |
|
|
605
|
+
| `contact.subscription_updated` | A contact's subscription changed |
|
|
606
|
+
| `contact.unsubscribed` | A contact unsubscribed |
|
|
607
|
+
| `message.received` | A message was received from a contact |
|
|
608
|
+
| `message.sent` | A message was sent to a contact |
|
|
609
|
+
| `message.sent_status` | Delivery status of a sent message changed |
|
|
610
|
+
| `whatsapp.message.session.sent_status` | Delivery status of a session message changed |
|
|
611
|
+
| `whatsapp.message.template.sent_status` | Delivery status of a template message changed |
|
|
612
|
+
| `whatsapp.message.template.approval_status` | A message template was approved or rejected |
|
|
613
|
+
| `conversation.change_folder` | A conversation was moved to another folder |
|
|
614
|
+
|
|
615
|
+
An unknown event name returns a `SendbeeRequestApiException`. For the authoritative
|
|
616
|
+
and up to date list, see the [documentation](https://developer.ainumber.com/#webhooks).
|
|
617
|
+
|
|
618
|
+
Once subscribed, see [Authenticate webhook request](#authenticate-webhook-request)
|
|
619
|
+
for how to verify the requests we send you.
|
|
620
|
+
|
|
560
621
|
## Misc
|
|
561
622
|
|
|
562
623
|
### <a href='#pagination'>Pagination</a>
|
|
@@ -642,7 +703,7 @@ except SendbeeRequestApiException as e:
|
|
|
642
703
|
|
|
643
704
|
### <a href='#authenticate-webhook-request'>Authenticate webhook request</a>
|
|
644
705
|
|
|
645
|
-
After activating your webhook URL in Sendbee Dashboard, we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
706
|
+
After activating your webhook URL in Sendbee Dashboard or with [Subscribe to webhook](#subscribe-webhook), we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
646
707
|
Every request that we make will have authorization token in header, like this:
|
|
647
708
|
|
|
648
709
|
```
|
|
@@ -665,9 +726,24 @@ if not api.auth.check_auth_token(token):
|
|
|
665
726
|
# error! authentication failed!
|
|
666
727
|
```
|
|
667
728
|
|
|
729
|
+
Requests to a webhook URL that has its own secret key also carry a second header,
|
|
730
|
+
`X-Auth-Token`, signed with that key instead of your account secret. You get the
|
|
731
|
+
key back as `webhook.secret_key` from [Subscribe to webhook](#subscribe-webhook).
|
|
732
|
+
Check it the same way, using `SendbeeAuth` directly:
|
|
733
|
+
|
|
734
|
+
```python
|
|
735
|
+
from sendbee_api import SendbeeAuth
|
|
736
|
+
|
|
737
|
+
secret_key = '...' # webhook.secret_key, store it when you subscribe
|
|
738
|
+
|
|
739
|
+
token = '...' # taken from the 'X-Auth-Token' request header
|
|
740
|
+
if not SendbeeAuth(secret_key).check_auth_token(token):
|
|
741
|
+
# error! authentication failed!
|
|
742
|
+
```
|
|
743
|
+
|
|
668
744
|
### <a href='#warnings'>Warnings</a>
|
|
669
745
|
|
|
670
|
-
Sometimes APi returns a
|
|
746
|
+
Sometimes APi returns a warning so you could be warned about something.
|
|
671
747
|
The waning is displayed in standard output:
|
|
672
748
|
|
|
673
749
|

|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/number-logo-tm.png" alt="AI Number">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# AI Number Python API Client
|
|
2
6
|
|
|
3
7
|
[](https://badge.fury.io/py/sendbee-api)
|
|
@@ -45,6 +49,10 @@
|
|
|
45
49
|
- [Managing chatbot (automated responses) status settings](#bot-on-off)
|
|
46
50
|
- [Get chatbot (automated responses) status](#bot-status)
|
|
47
51
|
|
|
52
|
+
#### Webhooks
|
|
53
|
+
|
|
54
|
+
- [Subscribe to webhook](#subscribe-webhook)
|
|
55
|
+
|
|
48
56
|
#### Mics
|
|
49
57
|
|
|
50
58
|
- [Pagination](#pagination)
|
|
@@ -522,6 +530,59 @@ response.chatbot_active # True/False
|
|
|
522
530
|
|
|
523
531
|
```
|
|
524
532
|
|
|
533
|
+
## Webhooks
|
|
534
|
+
|
|
535
|
+
### <a href='#subscribe-webhook'>Subscribe to webhook</a>
|
|
536
|
+
|
|
537
|
+
Register a URL of yours and subscribe it to a webhook event, so we start sending
|
|
538
|
+
you requests when that event happens. This does the same thing as activating a
|
|
539
|
+
webhook URL in the Sendbee Dashboard.
|
|
540
|
+
|
|
541
|
+
```python
|
|
542
|
+
webhook = api.subscribe_webhook(
|
|
543
|
+
url='https://example.com/sendbee/webhook',
|
|
544
|
+
event='message.received'
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
webhook.id # UUID of the webhook endpoint
|
|
548
|
+
webhook.url # the URL you registered
|
|
549
|
+
webhook.active # True/False
|
|
550
|
+
webhook.selected_webhooks # list of all events this URL is subscribed to
|
|
551
|
+
webhook.secret_key # key used to sign requests we send to your URL
|
|
552
|
+
webhook.created_at
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Both `url` and `event` are required. Calling it again for the same `url` is safe -
|
|
556
|
+
it will not create a duplicate, it just adds the new event to that URL (and
|
|
557
|
+
re-activates it if it was deactivated), so subscribe one event per call:
|
|
558
|
+
|
|
559
|
+
```python
|
|
560
|
+
for event in ('message.received', 'message.sent'):
|
|
561
|
+
api.subscribe_webhook(url='https://example.com/sendbee/webhook', event=event)
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Available events:
|
|
565
|
+
|
|
566
|
+
| Event | Description |
|
|
567
|
+
| --- | --- |
|
|
568
|
+
| `contact.created` | A new contact was created |
|
|
569
|
+
| `contact.subscription_created` | A contact subscribed |
|
|
570
|
+
| `contact.subscription_updated` | A contact's subscription changed |
|
|
571
|
+
| `contact.unsubscribed` | A contact unsubscribed |
|
|
572
|
+
| `message.received` | A message was received from a contact |
|
|
573
|
+
| `message.sent` | A message was sent to a contact |
|
|
574
|
+
| `message.sent_status` | Delivery status of a sent message changed |
|
|
575
|
+
| `whatsapp.message.session.sent_status` | Delivery status of a session message changed |
|
|
576
|
+
| `whatsapp.message.template.sent_status` | Delivery status of a template message changed |
|
|
577
|
+
| `whatsapp.message.template.approval_status` | A message template was approved or rejected |
|
|
578
|
+
| `conversation.change_folder` | A conversation was moved to another folder |
|
|
579
|
+
|
|
580
|
+
An unknown event name returns a `SendbeeRequestApiException`. For the authoritative
|
|
581
|
+
and up to date list, see the [documentation](https://developer.ainumber.com/#webhooks).
|
|
582
|
+
|
|
583
|
+
Once subscribed, see [Authenticate webhook request](#authenticate-webhook-request)
|
|
584
|
+
for how to verify the requests we send you.
|
|
585
|
+
|
|
525
586
|
## Misc
|
|
526
587
|
|
|
527
588
|
### <a href='#pagination'>Pagination</a>
|
|
@@ -607,7 +668,7 @@ except SendbeeRequestApiException as e:
|
|
|
607
668
|
|
|
608
669
|
### <a href='#authenticate-webhook-request'>Authenticate webhook request</a>
|
|
609
670
|
|
|
610
|
-
After activating your webhook URL in Sendbee Dashboard, we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
671
|
+
After activating your webhook URL in Sendbee Dashboard or with [Subscribe to webhook](#subscribe-webhook), we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
611
672
|
Every request that we make will have authorization token in header, like this:
|
|
612
673
|
|
|
613
674
|
```
|
|
@@ -630,9 +691,24 @@ if not api.auth.check_auth_token(token):
|
|
|
630
691
|
# error! authentication failed!
|
|
631
692
|
```
|
|
632
693
|
|
|
694
|
+
Requests to a webhook URL that has its own secret key also carry a second header,
|
|
695
|
+
`X-Auth-Token`, signed with that key instead of your account secret. You get the
|
|
696
|
+
key back as `webhook.secret_key` from [Subscribe to webhook](#subscribe-webhook).
|
|
697
|
+
Check it the same way, using `SendbeeAuth` directly:
|
|
698
|
+
|
|
699
|
+
```python
|
|
700
|
+
from sendbee_api import SendbeeAuth
|
|
701
|
+
|
|
702
|
+
secret_key = '...' # webhook.secret_key, store it when you subscribe
|
|
703
|
+
|
|
704
|
+
token = '...' # taken from the 'X-Auth-Token' request header
|
|
705
|
+
if not SendbeeAuth(secret_key).check_auth_token(token):
|
|
706
|
+
# error! authentication failed!
|
|
707
|
+
```
|
|
708
|
+
|
|
633
709
|
### <a href='#warnings'>Warnings</a>
|
|
634
710
|
|
|
635
|
-
Sometimes APi returns a
|
|
711
|
+
Sometimes APi returns a warning so you could be warned about something.
|
|
636
712
|
The waning is displayed in standard output:
|
|
637
713
|
|
|
638
714
|

|
|
@@ -3,13 +3,14 @@ import click
|
|
|
3
3
|
from sendbee_api.auth import SendbeeAuth
|
|
4
4
|
from sendbee_api.teams.client import Teams
|
|
5
5
|
from sendbee_api.contacts.client import Contacts
|
|
6
|
+
from sendbee_api.webhooks.client import Webhooks
|
|
6
7
|
from sendbee_api.rate_limit.client import RateLimit
|
|
7
8
|
from sendbee_api.automation.client import Automation
|
|
8
9
|
from sendbee_api.conversations.client import Messages
|
|
9
10
|
from sendbee_api.exceptions import SendbeeRequestApiException
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class Client(Contacts, Messages, Automation, Teams, RateLimit):
|
|
13
|
+
class Client(Contacts, Messages, Automation, Teams, Webhooks, RateLimit):
|
|
13
14
|
"""Main API class. Sets all API calls."""
|
|
14
15
|
|
|
15
16
|
base_url = 'api-v2.sendbee.io'
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from sendbee_api import constants
|
|
2
|
+
from sendbee_api.webhooks import models
|
|
3
|
+
from sendbee_api.bind import bind_request
|
|
4
|
+
from sendbee_api.webhooks import query_params
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Webhooks:
|
|
8
|
+
"""Api client for webhooks"""
|
|
9
|
+
|
|
10
|
+
subscribe_webhook = bind_request(
|
|
11
|
+
api_path='/webhooks/subscribe',
|
|
12
|
+
model=models.Webhook,
|
|
13
|
+
method=constants.RequestConst.POST,
|
|
14
|
+
query_parameters=query_params.SubscribeWebhook,
|
|
15
|
+
description='Api client for subscribing to a webhook event'
|
|
16
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from sendbee_api.models import Model
|
|
2
|
+
from sendbee_api.fields import (
|
|
3
|
+
TextField, BooleanField, DatetimeField, ListField
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Webhook(Model):
|
|
8
|
+
"""Data model for a webhook endpoint subscription"""
|
|
9
|
+
|
|
10
|
+
_id = TextField(index='id', desc='UUID')
|
|
11
|
+
_url = TextField(index='url', desc='Webhook endpoint URL')
|
|
12
|
+
_active = BooleanField(index='active', desc='Is the endpoint active')
|
|
13
|
+
_secret_key = TextField(
|
|
14
|
+
index='secret_key',
|
|
15
|
+
desc='Key that signs the X-Auth-Token header on inbound webhooks'
|
|
16
|
+
)
|
|
17
|
+
_selected_webhooks = ListField(
|
|
18
|
+
index='selected_webhooks', desc='Subscribed webhook event names'
|
|
19
|
+
)
|
|
20
|
+
_created_at = DatetimeField(
|
|
21
|
+
index='created_at', desc='Created at', format='%Y-%m-%d %H:%M:%'
|
|
22
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from sendbee_api.query_params import QueryParams
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SubscribeWebhook(QueryParams):
|
|
5
|
+
"""Parameters for subscribing to a webhook event"""
|
|
6
|
+
|
|
7
|
+
url = 'url', 'Webhook endpoint URL that will receive the events'
|
|
8
|
+
event = 'event', 'Webhook event name, e.g. "message.received". ' \
|
|
9
|
+
'See https://developer.ainumber.com/#webhooks'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sendbee_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Python client SDK for Sendbee Public API
|
|
5
5
|
Author-email: Sendbee ltd <info@sendbee.io>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,10 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
33
33
|
Requires-Dist: responses>=0.23; extra == "dev"
|
|
34
34
|
Dynamic: license-file
|
|
35
35
|
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="docs/images/number-logo-tm.png" alt="AI Number">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
36
40
|
# AI Number Python API Client
|
|
37
41
|
|
|
38
42
|
[](https://badge.fury.io/py/sendbee-api)
|
|
@@ -80,6 +84,10 @@ Dynamic: license-file
|
|
|
80
84
|
- [Managing chatbot (automated responses) status settings](#bot-on-off)
|
|
81
85
|
- [Get chatbot (automated responses) status](#bot-status)
|
|
82
86
|
|
|
87
|
+
#### Webhooks
|
|
88
|
+
|
|
89
|
+
- [Subscribe to webhook](#subscribe-webhook)
|
|
90
|
+
|
|
83
91
|
#### Mics
|
|
84
92
|
|
|
85
93
|
- [Pagination](#pagination)
|
|
@@ -557,6 +565,59 @@ response.chatbot_active # True/False
|
|
|
557
565
|
|
|
558
566
|
```
|
|
559
567
|
|
|
568
|
+
## Webhooks
|
|
569
|
+
|
|
570
|
+
### <a href='#subscribe-webhook'>Subscribe to webhook</a>
|
|
571
|
+
|
|
572
|
+
Register a URL of yours and subscribe it to a webhook event, so we start sending
|
|
573
|
+
you requests when that event happens. This does the same thing as activating a
|
|
574
|
+
webhook URL in the Sendbee Dashboard.
|
|
575
|
+
|
|
576
|
+
```python
|
|
577
|
+
webhook = api.subscribe_webhook(
|
|
578
|
+
url='https://example.com/sendbee/webhook',
|
|
579
|
+
event='message.received'
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
webhook.id # UUID of the webhook endpoint
|
|
583
|
+
webhook.url # the URL you registered
|
|
584
|
+
webhook.active # True/False
|
|
585
|
+
webhook.selected_webhooks # list of all events this URL is subscribed to
|
|
586
|
+
webhook.secret_key # key used to sign requests we send to your URL
|
|
587
|
+
webhook.created_at
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Both `url` and `event` are required. Calling it again for the same `url` is safe -
|
|
591
|
+
it will not create a duplicate, it just adds the new event to that URL (and
|
|
592
|
+
re-activates it if it was deactivated), so subscribe one event per call:
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
for event in ('message.received', 'message.sent'):
|
|
596
|
+
api.subscribe_webhook(url='https://example.com/sendbee/webhook', event=event)
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Available events:
|
|
600
|
+
|
|
601
|
+
| Event | Description |
|
|
602
|
+
| --- | --- |
|
|
603
|
+
| `contact.created` | A new contact was created |
|
|
604
|
+
| `contact.subscription_created` | A contact subscribed |
|
|
605
|
+
| `contact.subscription_updated` | A contact's subscription changed |
|
|
606
|
+
| `contact.unsubscribed` | A contact unsubscribed |
|
|
607
|
+
| `message.received` | A message was received from a contact |
|
|
608
|
+
| `message.sent` | A message was sent to a contact |
|
|
609
|
+
| `message.sent_status` | Delivery status of a sent message changed |
|
|
610
|
+
| `whatsapp.message.session.sent_status` | Delivery status of a session message changed |
|
|
611
|
+
| `whatsapp.message.template.sent_status` | Delivery status of a template message changed |
|
|
612
|
+
| `whatsapp.message.template.approval_status` | A message template was approved or rejected |
|
|
613
|
+
| `conversation.change_folder` | A conversation was moved to another folder |
|
|
614
|
+
|
|
615
|
+
An unknown event name returns a `SendbeeRequestApiException`. For the authoritative
|
|
616
|
+
and up to date list, see the [documentation](https://developer.ainumber.com/#webhooks).
|
|
617
|
+
|
|
618
|
+
Once subscribed, see [Authenticate webhook request](#authenticate-webhook-request)
|
|
619
|
+
for how to verify the requests we send you.
|
|
620
|
+
|
|
560
621
|
## Misc
|
|
561
622
|
|
|
562
623
|
### <a href='#pagination'>Pagination</a>
|
|
@@ -642,7 +703,7 @@ except SendbeeRequestApiException as e:
|
|
|
642
703
|
|
|
643
704
|
### <a href='#authenticate-webhook-request'>Authenticate webhook request</a>
|
|
644
705
|
|
|
645
|
-
After activating your webhook URL in Sendbee Dashboard, we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
706
|
+
After activating your webhook URL in Sendbee Dashboard or with [Subscribe to webhook](#subscribe-webhook), we will start sending requests on that URL depending on which webhook type is linked with that webhook URL.
|
|
646
707
|
Every request that we make will have authorization token in header, like this:
|
|
647
708
|
|
|
648
709
|
```
|
|
@@ -665,9 +726,24 @@ if not api.auth.check_auth_token(token):
|
|
|
665
726
|
# error! authentication failed!
|
|
666
727
|
```
|
|
667
728
|
|
|
729
|
+
Requests to a webhook URL that has its own secret key also carry a second header,
|
|
730
|
+
`X-Auth-Token`, signed with that key instead of your account secret. You get the
|
|
731
|
+
key back as `webhook.secret_key` from [Subscribe to webhook](#subscribe-webhook).
|
|
732
|
+
Check it the same way, using `SendbeeAuth` directly:
|
|
733
|
+
|
|
734
|
+
```python
|
|
735
|
+
from sendbee_api import SendbeeAuth
|
|
736
|
+
|
|
737
|
+
secret_key = '...' # webhook.secret_key, store it when you subscribe
|
|
738
|
+
|
|
739
|
+
token = '...' # taken from the 'X-Auth-Token' request header
|
|
740
|
+
if not SendbeeAuth(secret_key).check_auth_token(token):
|
|
741
|
+
# error! authentication failed!
|
|
742
|
+
```
|
|
743
|
+
|
|
668
744
|
### <a href='#warnings'>Warnings</a>
|
|
669
745
|
|
|
670
|
-
Sometimes APi returns a
|
|
746
|
+
Sometimes APi returns a warning so you could be warned about something.
|
|
671
747
|
The waning is displayed in standard output:
|
|
672
748
|
|
|
673
749
|

|
|
@@ -38,6 +38,10 @@ sendbee_api/teams/__init__.py
|
|
|
38
38
|
sendbee_api/teams/client.py
|
|
39
39
|
sendbee_api/teams/models.py
|
|
40
40
|
sendbee_api/teams/query_params.py
|
|
41
|
+
sendbee_api/webhooks/__init__.py
|
|
42
|
+
sendbee_api/webhooks/client.py
|
|
43
|
+
sendbee_api/webhooks/models.py
|
|
44
|
+
sendbee_api/webhooks/query_params.py
|
|
41
45
|
tests/test_auth.py
|
|
42
46
|
tests/test_bind_request.py
|
|
43
47
|
tests/test_client.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""End-to-end smoke tests: one happy-path per resource mixin.
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The 25 endpoint methods on `SendbeeApi` are all generated by `bind_request`,
|
|
4
4
|
which is covered in detail elsewhere. These tests verify the wiring per mixin
|
|
5
5
|
- that each `bind_request(...)` call site declares the right `api_path`,
|
|
6
6
|
`method`, `model`, and `QueryParams` - by exercising one representative
|
|
@@ -14,6 +14,7 @@ from sendbee_api.contacts.models import Contact
|
|
|
14
14
|
from sendbee_api.conversations.models import Conversation, SentMessage
|
|
15
15
|
from sendbee_api.automation.models import ChatbotActivityStatus
|
|
16
16
|
from sendbee_api.teams.models import Team
|
|
17
|
+
from sendbee_api.webhooks.models import Webhook
|
|
17
18
|
from sendbee_api.rate_limit.models import RateLimitError
|
|
18
19
|
from sendbee_api.models import ServerMessage
|
|
19
20
|
|
|
@@ -125,6 +126,43 @@ def test_teams_list_endpoint_returns_team_models(client, register, json_body):
|
|
|
125
126
|
assert response.models[0].name == "Support"
|
|
126
127
|
|
|
127
128
|
|
|
129
|
+
@responses.activate
|
|
130
|
+
def test_subscribe_webhook_endpoint_returns_webhook_model(client, register, json_body):
|
|
131
|
+
"""subscribe_webhook POSTs to /webhooks/subscribe and parses one Webhook.
|
|
132
|
+
|
|
133
|
+
The body is a bare object rather than a list because the backend's
|
|
134
|
+
PaginationJsonRenderer emits {"data": {...}} with no meta for non-GET
|
|
135
|
+
requests; Response.models wraps that lone dict before processing it.
|
|
136
|
+
"""
|
|
137
|
+
register("POST", "/webhooks/subscribe", body=json_body(
|
|
138
|
+
data={
|
|
139
|
+
"id": "wh1",
|
|
140
|
+
"active": True,
|
|
141
|
+
"url": "https://example.com/hook",
|
|
142
|
+
"secret_key": "s3cr3t",
|
|
143
|
+
"selected_webhooks": ["message.received"],
|
|
144
|
+
"created_at": "2026-07-27 10:11:12",
|
|
145
|
+
},
|
|
146
|
+
))
|
|
147
|
+
|
|
148
|
+
result = client.subscribe_webhook(
|
|
149
|
+
url="https://example.com/hook", event="message.received"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
assert responses.calls[0].request.method == "POST"
|
|
153
|
+
assert "/webhooks/subscribe" in responses.calls[0].request.url
|
|
154
|
+
|
|
155
|
+
body = ujson.loads(responses.calls[0].request.body)
|
|
156
|
+
assert body == {"url": "https://example.com/hook", "event": "message.received"}
|
|
157
|
+
|
|
158
|
+
assert isinstance(result, Webhook)
|
|
159
|
+
assert result.id == "wh1"
|
|
160
|
+
assert result.active is True
|
|
161
|
+
assert result.url == "https://example.com/hook"
|
|
162
|
+
assert result.secret_key == "s3cr3t"
|
|
163
|
+
assert result.selected_webhooks == ["message.received"]
|
|
164
|
+
|
|
165
|
+
|
|
128
166
|
@responses.activate
|
|
129
167
|
def test_rate_limit_error_test_endpoint_returns_rate_limit_error_model(client, register, json_body):
|
|
130
168
|
"""rate_limit_error_test hits /rate-limit/error-test on the RateLimit mixin."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|