Skip to content

Commit

Permalink
SMS: webhooks (#5)
Browse files Browse the repository at this point in the history
* feat: WebHooks
  • Loading branch information
JPPortier authored Nov 8, 2023
1 parent e104a21 commit a38b69b
Show file tree
Hide file tree
Showing 32 changed files with 1,516 additions and 0 deletions.
8 changes: 8 additions & 0 deletions client/src/main/com/sinch/sdk/domains/sms/SMSService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ public interface SMSService {
* @since 1.0
*/
BatchesService batches();

/**
* WebHooksService Service instance
*
* @return service instance for project
* @since 1.0
*/
WebHooksService webHooks();
}
103 changes: 103 additions & 0 deletions client/src/main/com/sinch/sdk/domains/sms/WebHooksService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.sinch.sdk.domains.sms;

import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.domains.sms.models.webhooks.BaseDeliveryReport;
import com.sinch.sdk.domains.sms.models.webhooks.BaseIncomingSMS;

/**
* WebHooks
*
* <p><b>Callbacks</b>
*
* <p>A callback is an HTTP POST request with a notification made by the Sinch SMS REST API to a URI
* of your choosing.
*
* <p>The REST API expects the receiving server to respond with a response code within the <code>2xx
* </code> success range. For <code>5xx</code> the callback will be retried. For <code>429</code>
* the callback will be retried and the throughput will be lowered. For other status codes in the
* <code>4xx</code> range the callback will not be retried. The first initial retry will happen 5
* seconds after the first try. The next attempt is after 10 seconds, then after 20 seconds, after
* 40 seconds, after 80 seconds, doubling on every attempt. The last retry will be at 81920 seconds
* (or 22 hours 45 minutes) after the initial failed attempt.
*
* <p>The SMS REST API offers the following callback options which can be configured for your
* account upon request to your account manager.
*
* <ul>
* <li>Callback with mutual authentication over TLS (HTTPS) connection by provisioning the
* callback URL with client keystore and password.
* <li>Callback with basic authentication by provisioning the callback URL with username and
* password.
* <li>Callback with OAuth 2.0 by provisioning the callback URL with username, password and the
* URL to fetch OAuth access token.
* <li>Callback using AWS SNS by provisioning the callback URL with an Access Key ID, Secret Key
* and Region.
* </ul>
*
* @see <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/">https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/</a>
* @since 1.0
*/
public interface WebHooksService {

/**
* Incoming SMS WebHook
*
* <p>An inbound message is a message sent to one of your short codes or long numbers from a
* mobile phone. To receive inbound message callbacks, a URL needs to be added to your REST API.
* This URL can be specified in your <a href="https://dashboard.sinch.com/sms/api">Dashboard</a>.
*
* @param jsonPayload The incoming message to your sinch number
* @return Decoded payload
* @see <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/operation/incomingSMS">https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/operation/incomingSMS</a>
* @since 1.0
*/
BaseIncomingSMS<?> incomingSMS(String jsonPayload) throws ApiMappingException;

/**
* Delivery Report WebHook
*
* <p>A delivery report contains the status and status code for each recipient of a batch. To get
* a delivery report callback for a message or batch of messages, set the <code>delivery_report
* </code> field accordingly when creating a batch.
*
* <p>The following is provided so you can better understand our webhooks/callbacks. Configuration
* of both webhooks and the type of delivery report requested happens when sending a batch.
*
* <p><b>Callback URL</b>
*
* <p>The callback URL can either be provided for each batch or provisioned globally for your
* account in your <a href="https://dashboard.sinch.com/sms/api/rest">Sinch Customer
* Dashboard</a>. Learn how to configure a webhook/callback <a
* href="https://community.sinch.com/t5/SMS/How-do-I-assign-a-callback-URL-to-an-SMS-service-plan/ta-p/8414">here</a>
*
* <p><b>Type</b>
*
* <p>The <code>type</code> is the type of delivery report webhook. The response will vary
* depending on the webhook delivery report you selected when the batch was sent, so choose the
* appropriate selection under "One of".
*
* <ul>
* <li>The <code>delivery_report_sms</code> and <code>delivery_report_mms</code> types are
* documented under <b>Delivery report</b>. These are reports containing <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Batches/#tag/Batches/operation/SendSMS!path=0/delivery_report&t=request">either
* a full report or summary report</a>, depending on your selection at the time the batch
* was sent.
* <li>The <code>recipient_delivery_report_sms</code> and <code>recipient_delivery_report_mms
* </code> delivery report types are documented under <b>Recipient delivery report</b>.
* These are delivery reports for recipient phone numbers. If you set <code>per_recipient
* </code> for the <code>delivery_report</code> parameter when sending the batch, a
* recipient report gets sent to you for each status change for each recipient in your
* batch. If you set <code>per_recipient_final</code>, a recipient report gets sent to you
* for the final status of each recipient in your batch.
* </ul>
*
* @param jsonPayload The incoming delivery report
* @return Decoded payload
* @see <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/operation/deliveryReport">https://developers.sinch.com/docs/sms/api-reference/sms/tag/Webhooks/#tag/Webhooks/operation/deliveryReport</a>
* @since 1.0
*/
BaseDeliveryReport deliveryReport(String jsonPayload) throws ApiMappingException;
}
10 changes: 10 additions & 0 deletions client/src/main/com/sinch/sdk/domains/sms/adapters/SMSService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.domains.sms.BatchesService;
import com.sinch.sdk.domains.sms.WebHooksService;
import com.sinch.sdk.models.Configuration;

public class SMSService implements com.sinch.sdk.domains.sms.SMSService {

private final Configuration configuration;
private final HttpClient httpClient;
private BatchesService batches;
private WebHooksService webHooks;

public SMSService(Configuration configuration, HttpClient httpClient) {
this.configuration = configuration;
Expand All @@ -23,4 +25,12 @@ public BatchesService batches() {
}
return this.batches;
}

@Override
public WebHooksService webHooks() {
if (null == this.webHooks) {
this.webHooks = new com.sinch.sdk.domains.sms.adapters.WebHooksService();
}
return this.webHooks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sinch.sdk.domains.sms.adapters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.sms.models.webhooks.BaseDeliveryReport;
import com.sinch.sdk.domains.sms.models.webhooks.BaseIncomingSMS;

public class WebHooksService implements com.sinch.sdk.domains.sms.WebHooksService {

@Override
public BaseIncomingSMS<?> incomingSMS(String jsonPayload) throws ApiMappingException {
try {
BaseIncomingSMS<?> generic =
Mapper.getInstance().readValue(jsonPayload, BaseIncomingSMS.class);
if (null == generic && !StringUtil.isEmpty(jsonPayload)) {
throw new ApiMappingException(jsonPayload, null);
}
return generic;
} catch (JsonProcessingException e) {
throw new ApiMappingException(jsonPayload, e);
}
}

@Override
public BaseDeliveryReport deliveryReport(String jsonPayload) throws ApiMappingException {
try {
BaseDeliveryReport generic =
Mapper.getInstance().readValue(jsonPayload, BaseDeliveryReport.class);
if (null == generic && !StringUtil.isEmpty(jsonPayload)) {
throw new ApiMappingException(jsonPayload, null);
}
return generic;
} catch (JsonProcessingException e) {
throw new ApiMappingException(jsonPayload, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.sinch.sdk.domains.sms.models;

import com.sinch.sdk.core.utils.EnumDynamic;
import com.sinch.sdk.core.utils.EnumSupportDynamic;
import java.util.Arrays;
import java.util.stream.Stream;

/**
* The status field describes which state a particular message is in. Note that statuses of type
* Intermediate will only be reported if you request a status <code>per_recipient</code> or <code>
* per_recipient_final</code> ( @see <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Delivery-reports/#tag/Delivery-reports/operation/GetDeliveryReportByBatchId">Retrieve
* a recipient delivery report</a>).
*
* @see <a
* href="https://developers.sinch.com/docs/sms/api-reference/sms/tag/Delivery-reports/#tag/Delivery-reports/section/Delivery-report-statuses">https://developers.sinch.com/docs/sms/api-reference/sms/tag/Delivery-reports/#tag/Delivery-reports/section/Delivery-report-statuses</a>
* @since 1.0
*/
public class DeliveryReportStatus extends EnumDynamic<DeliveryReportStatus> {

/**
* Message is queued within REST API system and will be dispatched according to the rate of the
* account.
*/
public static final DeliveryReportStatus QUEUED = new DeliveryReportStatus("Queued");
/** Message has been dispatched and accepted for delivery by the SMSC. */
public static final DeliveryReportStatus DISPATCHED = new DeliveryReportStatus("Dispatched");
/** Message was aborted before reaching the SMSC. */
public static final DeliveryReportStatus ABORTED = new DeliveryReportStatus("Aborted");
/** Message was cancelled by user before reaching SMSC. */
public static final DeliveryReportStatus CANCELLED = new DeliveryReportStatus("Cancelled");
/** Message was rejected by the SMSC. */
public static final DeliveryReportStatus REJECTED = new DeliveryReportStatus("Rejected");
/**
* Message has been deleted. Message was deleted by a remote SMSC. This may happen if the
* destination is an invalid MSISDN or opted out subscriber.
*/
public static final DeliveryReportStatus DELETED = new DeliveryReportStatus("Deleted");
/** Message has been delivered. */
public static final DeliveryReportStatus DELIVERED = new DeliveryReportStatus("Delivered");
/** Message failed to be delivered. */
public static final DeliveryReportStatus FAILED = new DeliveryReportStatus("Failed");
/**
* Message expired before delivery to the SMSC. This may happen if the expiry time for the message
* was very short.
*/
public static final DeliveryReportStatus EXPIRED = new DeliveryReportStatus("Expired");
/**
* Message was delivered to the SMSC but no Delivery Receipt has been received or a Delivery
* Receipt that couldn't be interpreted was received.
*/
public static final DeliveryReportStatus UNKNOWN = new DeliveryReportStatus("Unknown");

private static final EnumSupportDynamic<DeliveryReportStatus> ENUM_SUPPORT =
new EnumSupportDynamic<>(
DeliveryReportStatus.class,
DeliveryReportStatus::new,
Arrays.asList(
QUEUED,
DISPATCHED,
ABORTED,
CANCELLED,
REJECTED,
DELETED,
DELIVERED,
FAILED,
EXPIRED,
UNKNOWN));

private DeliveryReportStatus(String value) {
super(value);
}

public static Stream<DeliveryReportStatus> values() {
return ENUM_SUPPORT.values();
}

public static DeliveryReportStatus from(String value) {
return ENUM_SUPPORT.from(value);
}

public static String valueOf(DeliveryReportStatus e) {
return ENUM_SUPPORT.valueOf(e);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.sinch.sdk.domains.sms.models.webhooks;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.Objects;
import java.util.Optional;

/**
* Base class for Delivery Report WebHook
*
* @since 1.0
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(
value = DeliveryReportRecipientSMS.class,
name = "recipient_delivery_report_sms"),
@JsonSubTypes.Type(
value = DeliveryReportRecipientMMS.class,
name = "recipient_delivery_report_mms"),
@JsonSubTypes.Type(value = DeliveryReportSMS.class, name = "delivery_report_sms"),
@JsonSubTypes.Type(value = DeliveryReportMMS.class, name = "delivery_report_mms")
})
public abstract class BaseDeliveryReport {

static final String JSON_PROPERTY_BATCH_ID = "batch_id";
private final String batchId;
static final String JSON_PROPERTY_CLIENT_REFERENCE = "client_reference";
private final String clientReference;

/**
* @param batchId <code>Required.</code> The ID of the batch this delivery report belongs to.
* @param clientReference The client identifier of the batch this delivery report belongs to, if
* set when submitting batch.
*/
@JsonCreator
public BaseDeliveryReport(
@JsonProperty(JSON_PROPERTY_BATCH_ID) String batchId,
@JsonProperty(JSON_PROPERTY_CLIENT_REFERENCE) String clientReference) {
Objects.requireNonNull(batchId);
this.batchId = batchId;
this.clientReference = clientReference;
}

public String getBatchId() {
return batchId;
}

public Optional<String> getClientReference() {
return Optional.ofNullable(clientReference);
}

@Override
public String toString() {
return "BaseDeliveryReport{"
+ "batchId='"
+ batchId
+ '\''
+ ", clientReference='"
+ clientReference
+ '\''
+ '}';
}
}
Loading

0 comments on commit a38b69b

Please sign in to comment.