-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature (template/server): Provide a server side application template for webhooks #10
Changes from 2 commits
023a04a
8ddc0be
548eb80
2ce91f6
ccbe046
d80d14a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Backend application built using Sinch Java SDK to handle incoming webhooks | ||
|
||
This directory contains a server application based onto [Sinch SDK java](https://github.com/sinch/sinch-sdk-java) | ||
|
||
## Requirements | ||
|
||
- JDK 8 or later (Sinch SDK Java is requiring java 8 only but client application can use latest available version) | ||
- [Maven](https://maven.apache.org/) | ||
- [SpringBoot](https://spring.io/projects/spring-boot) | ||
- [Sinch account](https://dashboard.sinch.com) | ||
- [ngrok](https://ngrok.com/docs) | ||
|
||
## Configuration | ||
|
||
### Configure application settings | ||
|
||
com.mycompany.app.Application settings are using the `SpringBoot` configuration file: [`application.yaml`](src/main/resources/application.yaml) file and enable to configure: | ||
|
||
#### Sinch credentials | ||
Located in `credentials` section (*you can find all of the credentials you need on your [Sinch dashboard](https://dashboard.sinch.com)*): | ||
- `project-id`: YOUR_project_id | ||
- `key-id`: YOUR_access_key_id | ||
- `key-secret`: YOUR_access_key_secret | ||
|
||
#### Server port | ||
<em>Default: 8090</em> | ||
|
||
Located in `server` section: | ||
- port: The port to be used to listen to incoming requests. <em>Default: 8090</em> | ||
|
||
## Usage | ||
|
||
### Start server | ||
1. Edit configuration file | ||
|
||
See above for Configuration paragraph | ||
|
||
2. Start server locally. | ||
|
||
Compile and run the application as server locally. | ||
```bash | ||
mvn spring-boot:run | ||
``` | ||
### EndPoints | ||
When server is online, declared controllers will respond to following endpoints | ||
|
||
| Service | Endpoint | | ||
|--------------|--------------------| | ||
| Numbers | /NumbersEvent | | ||
| SMS | /SmsEvent | | ||
| Verification | /VerificationEvent | | ||
| Voice | /VoiceEvent | | ||
|
||
## Use ngrok to forward request to local server | ||
|
||
Forwarding request to same `8090` port used above: | ||
|
||
*Note: The `8090` value is coming from default config and can be changed (see [Server port](#Server port) configuration section)* | ||
|
||
```bash | ||
ngrok http 8090 | ||
``` | ||
|
||
ngrok output will contains output like: | ||
``` | ||
ngrok (Ctrl+C to quit) | ||
|
||
... | ||
Forwarding https://0e64-78-117-86-140.ngrok-free.app -> http://localhost:8090 | ||
|
||
``` | ||
The line | ||
``` | ||
Forwarding https://0e64-78-117-86-140.ngrok-free.app -> http://localhost:8090 | ||
``` | ||
Contains `https://0e64-78-117-86-140.ngrok-free.app` value. | ||
|
||
This value must be used to configure callback's URL from your [Sinch dashboard](https://dashboard.sinch.com/sms/api/services) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-parent</artifactId> | ||
<version>3.2.5</version> | ||
<relativePath/> <!-- lookup parent from repository --> | ||
</parent> | ||
|
||
<groupId>my.company.com</groupId> | ||
<artifactId>sinch-java-sdk-server-application</artifactId> | ||
<version>0.0.1-SNAPSHOT</version> | ||
<name>Sinch Java SDK Server Application</name> | ||
|
||
<properties> | ||
<sinch.sdk.java.version>[1.0.0,)</sinch.sdk.java.version> | ||
<java.version>21</java.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.sinch.sdk</groupId> | ||
<artifactId>sinch-sdk-java</artifactId> | ||
<version>${sinch.sdk.java.version}</version> | ||
</dependency> | ||
|
||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-maven-plugin</artifactId> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.mycompany.app; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
@SpringBootApplication | ||
public class Application { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(Application.class, args); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.mycompany.app; | ||
|
||
import com.sinch.sdk.SinchClient; | ||
import com.sinch.sdk.core.utils.StringUtil; | ||
import com.sinch.sdk.models.Configuration; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
|
||
@org.springframework.context.annotation.Configuration | ||
public class Config { | ||
|
||
@Value("${credentials.project-id}") | ||
String projectId; | ||
|
||
@Value("${credentials.key-id}") | ||
String keyId; | ||
|
||
@Value("${credentials.key-secret}") | ||
String keySecret; | ||
|
||
@Value("${credentials.application-api-key}") | ||
String applicationKey; | ||
|
||
@Value("${credentials.application-api-secret}") | ||
String applicationSecret; | ||
|
||
@Bean | ||
public SinchClient sinchClient() { | ||
|
||
Configuration.Builder builder = Configuration.builder(); | ||
|
||
if (!StringUtil.isEmpty(projectId)) { | ||
builder.setProjectId(projectId); | ||
} | ||
|
||
if (!StringUtil.isEmpty(keyId)) { | ||
builder.setKeyId(keyId); | ||
} | ||
if (!StringUtil.isEmpty(keySecret)) { | ||
builder.setKeySecret(keySecret); | ||
} | ||
|
||
if (!StringUtil.isEmpty(applicationKey)) { | ||
builder.setApplicationKey(applicationKey); | ||
} | ||
|
||
if (!StringUtil.isEmpty(applicationSecret)) { | ||
builder.setApplicationSecret(applicationSecret); | ||
} | ||
|
||
return new SinchClient(builder.build()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.mycompany.app.numbers; | ||
|
||
import com.sinch.sdk.SinchClient; | ||
import com.sinch.sdk.domains.numbers.api.v1.WebHooksService; | ||
import com.sinch.sdk.domains.numbers.models.v1.webhooks.NumberEvent; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController("Numbers") | ||
public class Controller { | ||
|
||
private final SinchClient sinchClient; | ||
private final WebhooksBusinessLogic webhooksBusinessLogic; | ||
|
||
@Autowired | ||
public Controller(SinchClient sinchClient, WebhooksBusinessLogic webhooksBusinessLogic) { | ||
this.sinchClient = sinchClient; | ||
this.webhooksBusinessLogic = webhooksBusinessLogic; | ||
} | ||
|
||
@PostMapping( | ||
value = "/NumbersEvent", | ||
consumes = MediaType.APPLICATION_JSON_VALUE, | ||
produces = MediaType.APPLICATION_JSON_VALUE) | ||
public void NumbersEvent(@RequestBody String body) { | ||
|
||
WebHooksService webhooks = sinchClient.numbers().v1().webhooks(); | ||
|
||
// decode the request payload | ||
NumberEvent event = webhooks.parseEvent(body); | ||
|
||
// let business layer process the request | ||
webhooksBusinessLogic.numbersEvent(event); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although thanks to the Spring Boot configuration (typically the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very good [educational] idea 👍 |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.mycompany.app.numbers; | ||
|
||
import com.sinch.sdk.domains.numbers.models.v1.webhooks.NumberEvent; | ||
import java.util.logging.Logger; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component("NumbersWebhooksBusinessLogic") | ||
public class WebhooksBusinessLogic { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(WebhooksBusinessLogic.class.getName()); | ||
|
||
public void numbersEvent(NumberEvent event) { | ||
|
||
LOGGER.info("Handle event :" + event); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.mycompany.app.sms; | ||
|
||
import com.sinch.sdk.SinchClient; | ||
import com.sinch.sdk.domains.sms.WebHooksService; | ||
import com.sinch.sdk.domains.sms.models.DeliveryReportBatch; | ||
import com.sinch.sdk.domains.sms.models.DeliveryReportRecipient; | ||
import com.sinch.sdk.domains.sms.models.InboundBinary; | ||
import com.sinch.sdk.domains.sms.models.InboundText; | ||
import com.sinch.sdk.domains.sms.models.webhooks.WebhooksEvent; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController("SMS") | ||
public class Controller { | ||
|
||
private final SinchClient sinchClient; | ||
private final WebhooksBusinessLogic webhooksBusinessLogic; | ||
|
||
@Autowired | ||
public Controller(SinchClient sinchClient, WebhooksBusinessLogic webhooksBusinessLogic) { | ||
this.sinchClient = sinchClient; | ||
this.webhooksBusinessLogic = webhooksBusinessLogic; | ||
} | ||
|
||
@PostMapping( | ||
value = "/SmsEvent", | ||
consumes = MediaType.APPLICATION_JSON_VALUE, | ||
produces = MediaType.APPLICATION_JSON_VALUE) | ||
public void smsDeliveryEvent(@RequestBody String body) { | ||
|
||
WebHooksService webhooks = sinchClient.sms().webHooks(); | ||
|
||
// decode the request payload | ||
WebhooksEvent event = webhooks.parse(body); | ||
|
||
// let business layer process the request | ||
switch (event) { | ||
case InboundBinary e -> webhooksBusinessLogic.processInboundEvent(e); | ||
case InboundText e -> webhooksBusinessLogic.processInboundEvent(e); | ||
case DeliveryReportRecipient e -> webhooksBusinessLogic.processDeliveryReportEvent(e); | ||
case DeliveryReportBatch e -> webhooksBusinessLogic.processDeliveryReportEvent(e); | ||
default -> throw new IllegalStateException("Unexpected value: " + event); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.mycompany.app.sms; | ||
|
||
import com.sinch.sdk.domains.sms.models.DeliveryReportBatch; | ||
import com.sinch.sdk.domains.sms.models.DeliveryReportRecipient; | ||
import com.sinch.sdk.domains.sms.models.InboundBinary; | ||
import com.sinch.sdk.domains.sms.models.InboundText; | ||
import com.sinch.sdk.domains.sms.models.webhooks.WebhooksEvent; | ||
import java.util.logging.Logger; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component("SMSWebhooksBusinessLogic") | ||
public class WebhooksBusinessLogic { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(WebhooksBusinessLogic.class.getName()); | ||
|
||
public void processInboundEvent(InboundText event) { | ||
trace(event); | ||
} | ||
|
||
public void processInboundEvent(InboundBinary event) { | ||
trace(event); | ||
} | ||
|
||
public void processDeliveryReportEvent(DeliveryReportRecipient event) { | ||
trace(event); | ||
} | ||
|
||
public void processDeliveryReportEvent(DeliveryReportBatch event) { | ||
trace(event); | ||
} | ||
|
||
private void trace(WebhooksEvent event) { | ||
LOGGER.info("Handle event :" + event); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, the incoming request should be verified before being processed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
Created a ticket to add this missing feature to webhooks service
Thank you to highlight this