Skip to content
Merged
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
10 changes: 5 additions & 5 deletions examples/sinch_events/.env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Server Configuration
SERVER_PORT =

# Webhook Configuration
# The secret value used for webhook calls validation
# Sinch Event Configuration
# The secret value used for Sinch Event callback validation
# See https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/
NUMBERS_WEBHOOKS_SECRET = NUMBERS_WEBHOOKS_SECRET
NUMBERS_SINCH_EVENT_SECRET = NUMBERS_SINCH_EVENT_SECRET
# See https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/section/Callbacks
SMS_WEBHOOKS_SECRET = SMS_WEBHOOKS_SECRET
SMS_SINCH_EVENT_SECRET = SMS_SINCH_EVENT_SECRET
# See https://developers.sinch.com/docs/conversation/callbacks
CONVERSATION_WEBHOOKS_SECRET = CONVERSATION_WEBHOOKS_SECRET
CONVERSATION_SINCH_EVENT_SECRET = CONVERSATION_SINCH_EVENT_SECRET
32 changes: 16 additions & 16 deletions examples/sinch_events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
This directory contains a server application built with [Sinch Python SDK](https://github.com/sinch/sinch-sdk-python)
to process incoming webhooks from Sinch services.

The webhook handlers are organized by service:
- **SMS**: Handlers for SMS webhook events (`sms_api/`)
- **Numbers**: Handlers for Numbers API webhook events (`numbers_api/`)
- **Conversation**: Handlers for Conversation API webhook events (`conversation_api/`)
The Sinch Events Handlers are organized by service:
- **SMS**: Handlers for SMS events (`sms_api/`)
- **Numbers**: Handlers for Numbers API events (`numbers_api/`)
- **Conversation**: Handlers for Conversation API events (`conversation_api/`)

This directory contains both the webhook handlers and the server application (`server.py`) that uses them.
This directory contains both the Event handlers and the server application (`server.py`) that uses them.

## Requirements

Expand All @@ -32,17 +32,17 @@ This directory contains both the webhook handlers and the server application (`s
- Controller Settings
- Numbers controller: Set the `numbers` Sinch Event secret. You can retrieve it using the `/event_destination` endpoint (see SDK implementation: [event_destinations_apis.py](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/event_destinations_apis.py); for additional details, refer to the [Numbers API callbacks documentation](https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/)):
```
NUMBERS_WEBHOOKS_SECRET=Your Sinch Numbers Webhook Secret
NUMBERS_SINCH_EVENT_SECRET=Your Sinch Numbers Sinch Event Secret
```
- SMS controller: To configure the `sms` webhooks secret, contact your account manager to enable authentication for SMS callbacks. For more details, refer to
- SMS controller: To configure the `sms` Sinch Event secret, contact your account manager to enable authentication for SMS callbacks. For more details, refer to
[SMS API](https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/section/Callbacks),

```
SMS_WEBHOOKS_SECRET=Your Sinch SMS Webhook Secret
SMS_SINCH_EVENT_SECRET=Your Sinch SMS Sinch Event Secret
```
- Conversation controller: Set the webhook secret you configured when creating the webhook (see [Conversation API callbacks](https://developers.sinch.com/docs/conversation/callbacks)):
```
CONVERSATION_WEBHOOKS_SECRET=Your Conversation Webhook Secret
CONVERSATION_SINCH_EVENT_SECRET=Your Conversation Sinch Event Secret
```

## Usage
Expand Down Expand Up @@ -96,18 +96,18 @@ ngrok
...
Forwarding https://adbd-79-148-170-158.ngrok-free.app -> http://localhost:3001
```
Use the `https` forwarding URL in your callback configuration. For example:
Use the `https` forwarding URL in your event destination configuration. For example:
- Numbers: https://adbd-79-148-170-158.ngrok-free.app/NumbersEvent
- SMS: https://adbd-79-148-170-158.ngrok-free.app/SmsEvent
- Conversation: https://adbd-79-148-170-158.ngrok-free.app/ConversationEvent

Use this value to configure the callback URLs:
- **Numbers**: Set the `callback_url` parameter when renting or updating a number via the SDK (e.g., `available_numbers_apis` rent/update flow: [rent](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/available_numbers_apis.py#L69), [update](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/available_numbers_apis.py#L89)); you can also update active numbers via `active_numbers_apis` ([example](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/active_numbers_apis.py#L64)).
- **SMS**: Set the `callback_url` parameter when configuring your SMS service plan via the SDK (see `batches_apis` examples: [send/dry-run callbacks](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/sms/api/v1/batches_apis.py#L146), [update/replace callbacks](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/sms/api/v1/batches_apis.py#L491)); you can also set it directly via the SMS API.
Use this value to configure the Sinch Events URLs:
- **Numbers**: Set the `event_destination_target` parameter when renting or updating a number via the SDK (e.g., `available_numbers_apis` rent/update flow: [rent](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/available_numbers_apis.py#L69), [update](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/available_numbers_apis.py#L89)); you can also update active numbers via `active_numbers_apis` ([example](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/active_numbers_apis.py#L64)).
- **SMS**: Set the `event_destination_target` parameter when configuring your SMS service plan via the SDK (see `batches_apis` examples: [send/dry-run callbacks](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/sms/api/v1/batches_apis.py#L146), [update/replace callbacks](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/sms/api/v1/batches_apis.py#L491)); you can also set it directly via the SMS API.
- **Conversation**: Set the `callback_url` parameter when sending a message via the SDK (see `messages_apis` example: [send_text_message](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/conversation/api/v1/messages_apis.py#L420)).

You can also set these callback URLs in the Sinch dashboard; the API parameters above override the default values configured there.
You can also set these Sinch Events URLs in the Sinch dashboard; the API parameters above override the default values configured there.

> **Note**: If you have set a webhook secret (e.g., `SMS_WEBHOOKS_SECRET`), the webhook URL must be configured in the Sinch dashboard
> and cannot be overridden via API parameters. The webhook secret is used to validate incoming webhook requests,
> **Note**: If you have set a Sinch Event secret (e.g., `SMS_SINCH_EVENT_SECRET`), the Sinch Event URL must be configured in the Sinch dashboard
> and cannot be overridden via API parameters. The Sinch Event secret is used to validate incoming webhook requests,
> and the URL associated with it must be set in the dashboard.
10 changes: 6 additions & 4 deletions examples/sinch_events/conversation_api/controller.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
from flask import request, Response
from webhooks.conversation_api.server_business_logic import handle_conversation_event
from sinch_events.conversation_api.server_business_logic import handle_conversation_event


class ConversationController:
def __init__(self, sinch_client, webhooks_secret):
def __init__(self, sinch_client, sinch_event_secret):
self.sinch_client = sinch_client
self.webhooks_secret = webhooks_secret
self.sinch_event_secret = sinch_event_secret
self.logger = self.sinch_client.configuration.logger

def conversation_event(self):
headers = dict(request.headers)
raw_body = request.raw_body if request.raw_body else b""

webhooks_service = self.sinch_client.conversation.webhooks(self.webhooks_secret)
webhooks_service = self.sinch_client.conversation.webhooks(
self.sinch_event_secret
)

# Set to True to enforce signature validation (recommended in production)
ensure_valid_signature = False
Expand Down
8 changes: 4 additions & 4 deletions examples/sinch_events/numbers_api/controller.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
from flask import request, Response
from webhooks.numbers_api.server_business_logic import handle_numbers_event
from sinch_events.numbers_api.server_business_logic import handle_numbers_event


class NumbersController:
def __init__(self, sinch_client, webhooks_secret):
def __init__(self, sinch_client, sinch_event_secret):
self.sinch_client = sinch_client
self.webhooks_secret = webhooks_secret
self.sinch_event_secret = sinch_event_secret
self.logger = self.sinch_client.configuration.logger

def numbers_event(self):
headers = dict(request.headers)
raw_body = request.raw_body if request.raw_body else b""

sinch_events_service = self.sinch_client.numbers.sinch_events(
self.webhooks_secret
self.sinch_event_secret
)

ensure_valid_authentication = False
Expand Down
24 changes: 13 additions & 11 deletions examples/sinch_events/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,35 @@
import sys
from pathlib import Path

# Add examples directory to Python path to allow importing webhooks
# Add examples directory to Python path to allow importing sinch_events
examples_dir = Path(__file__).resolve().parent.parent
if str(examples_dir) not in sys.path:
sys.path.insert(0, str(examples_dir))

from flask import Flask, request
from webhooks.numbers_api.controller import NumbersController
from webhooks.sms_api.controller import SmsController
from webhooks.conversation_api.controller import ConversationController
from webhooks.sinch_client_helper import get_sinch_client, load_config
from sinch_events.numbers_api.controller import NumbersController
from sinch_events.sms_api.controller import SmsController
from sinch_events.conversation_api.controller import ConversationController
from sinch_events.sinch_client_helper import get_sinch_client, load_config

app = Flask(__name__)

config = load_config()
port = int(config.get('SERVER_PORT') or 3001)
numbers_webhooks_secret = config.get('NUMBERS_WEBHOOKS_SECRET')
sms_webhooks_secret = config.get('SMS_WEBHOOKS_SECRET')
conversation_webhooks_secret = config.get('CONVERSATION_WEBHOOKS_SECRET')
numbers_sinch_event_secret = config.get('NUMBERS_SINCH_EVENT_SECRET')
sms_sinch_event_secret = config.get('SMS_SINCH_EVENT_SECRET')
conversation_sinch_event_secret = config.get('CONVERSATION_SINCH_EVENT_SECRET')
sinch_client = get_sinch_client(config)

# Set up logging at the INFO level
logging.basicConfig()
sinch_client.configuration.logger.setLevel(logging.INFO)

numbers_controller = NumbersController(sinch_client, numbers_webhooks_secret)
sms_controller = SmsController(sinch_client, sms_webhooks_secret)
conversation_controller = ConversationController(sinch_client, conversation_webhooks_secret or '')
numbers_controller = NumbersController(sinch_client, numbers_sinch_event_secret)
sms_controller = SmsController(sinch_client, sms_sinch_event_secret)
conversation_controller = ConversationController(
sinch_client, conversation_sinch_event_secret or ''
)


# Middleware to capture raw body
Expand Down
4 changes: 2 additions & 2 deletions examples/sinch_events/sinch_client_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

def load_config() -> dict[str, str]:
"""
Load configuration from the .env file in the webhooks directory.
Load configuration from the .env file in the sinch_events directory.

Returns:
dict[str, str]: Dictionary containing configuration values
Expand All @@ -15,7 +15,7 @@ def load_config() -> dict[str, str]:
env_file = current_dir / '.env'

if not env_file.exists():
raise FileNotFoundError(f"Could not find .env file in webhooks directory: {env_file}")
raise FileNotFoundError(f"Could not find .env file in sinch_events directory: {env_file}")

config_dict = dotenv_values(env_file)

Expand Down
12 changes: 6 additions & 6 deletions examples/sinch_events/sms_api/controller.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
from flask import request, Response
from webhooks.sms_api.server_business_logic import (
from sinch_events.sms_api.server_business_logic import (
handle_sms_event,
)


class SmsController:
def __init__(self, sinch_client, webhooks_secret):
def __init__(self, sinch_client, sinch_event_secret):
self.sinch_client = sinch_client
self.webhooks_secret = webhooks_secret
self.sinch_event_secret = sinch_event_secret
self.logger = self.sinch_client.configuration.logger

def sms_event(self):
headers = dict(request.headers)
raw_body = request.raw_body if request.raw_body else b""

webhooks_service = self.sinch_client.sms.webhooks(self.webhooks_secret)
sinch_events_service = self.sinch_client.sms.sinch_events(self.sinch_event_secret)

# Signature headers may be absent unless your account manager enables them
# (see README: Configuration -> Controller Settings -> SMS controller);
# leave auth disabled here unless SMS callbacks are configured.
ensure_valid_authentication = False
if ensure_valid_authentication:
valid_auth = webhooks_service.validate_authentication_header(
valid_auth = sinch_events_service.validate_authentication_header(
headers=headers,
json_payload=raw_body,
)

if not valid_auth:
return Response(status=401)

event = webhooks_service.parse_event(raw_body, headers)
event = sinch_events_service.parse_event(raw_body, headers)

handle_sms_event(sms_event=event, logger=self.logger)

Expand Down
8 changes: 5 additions & 3 deletions examples/sinch_events/sms_api/server_business_logic.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from sinch.domains.sms.webhooks.v1.events.sms_webhooks_event import IncomingSMSWebhookEvent
from sinch.domains.sms.sinch_events.v1.events.sms_sinch_event import (
IncomingSMSSinchEvent,
)


def handle_sms_event(sms_event: IncomingSMSWebhookEvent, logger):
def handle_sms_event(sms_event: IncomingSMSSinchEvent, logger):
"""
This method handles an SMS event.
Args:
sms_event (SmsWebhooksEvent): The SMS event data.
sms_event (IncomingSMSSinchEvent): The SMS event data.
logger (logging.Logger, optional): Logger instance for logging. Defaults to None.
"""
logger.info(f'Handling SMS event:\n{sms_event.model_dump_json(indent=2)}')
Loading
Loading