diff --git a/client/src/main/com/sinch/sdk/domains/numbers/CallbackConfigurationService.java b/client/src/main/com/sinch/sdk/domains/numbers/CallbackConfigurationService.java
index 78aeab9d..df2796d5 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/CallbackConfigurationService.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/CallbackConfigurationService.java
@@ -8,7 +8,7 @@
* Callback Configuration Service
*
* @see https://developers.sinch.com/docs/numbers/api-reference/callbacks-numbers/tag/Callback-Configuration/
+ * href="https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/">https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/
*/
public interface CallbackConfigurationService {
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/NumbersService.java
index b66dc84f..7789ccaa 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/NumbersService.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/NumbersService.java
@@ -40,4 +40,12 @@ public interface NumbersService {
* @since 1.0
*/
CallbackConfigurationService callback();
+
+ /**
+ * Webhooks helpers instance
+ *
+ * @return instance service related to webhooks helpers
+ * @since 1.0
+ */
+ WebHooksService webhooks();
}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/WebHooksService.java b/client/src/main/com/sinch/sdk/domains/numbers/WebHooksService.java
new file mode 100644
index 00000000..7c31e05f
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/WebHooksService.java
@@ -0,0 +1,28 @@
+package com.sinch.sdk.domains.numbers;
+
+import com.sinch.sdk.core.exceptions.ApiMappingException;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+
+/**
+ * Webhooks service
+ *
+ *
Callback events are used to get notified about Numbers usage according to your configured
+ * callback URL
+ *
+ *
see https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/#tag/Callbacks/operation/ImportedNumberService_EventsCallback
+ *
+ * @since 1.0
+ */
+public interface WebHooksService {
+
+ /**
+ * This function can be called to deserialize received payload onto callback. Function return Java
+ * class instance from un-serialized payload
+ *
+ * @param jsonPayload Received payload to be un-serialized
+ * @return The decoded event notification instance class
+ * @since 1.0
+ */
+ EventNotification unserializeEventNotification(String jsonPayload) throws ApiMappingException;
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java
index a5b83af2..44216ac2 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/NumbersService.java
@@ -18,6 +18,8 @@ public class NumbersService implements com.sinch.sdk.domains.numbers.NumbersServ
private ActiveNumberService active;
private AvailableRegionService regions;
private CallbackConfigurationService callback;
+ private WebHooksService webhooks;
+
private final Map authManagers;
public NumbersService(Configuration configuration, HttpClient httpClient) {
@@ -57,4 +59,12 @@ public CallbackConfigurationService callback() {
}
return this.callback;
}
+
+ public WebHooksService webhooks() {
+
+ if (null == this.webhooks) {
+ this.webhooks = new WebHooksService();
+ }
+ return this.webhooks;
+ }
}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/WebHooksService.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/WebHooksService.java
new file mode 100644
index 00000000..fcbfa31b
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/WebHooksService.java
@@ -0,0 +1,28 @@
+package com.sinch.sdk.domains.numbers.adapters;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.sinch.sdk.core.exceptions.ApiMappingException;
+import com.sinch.sdk.core.utils.databind.Mapper;
+import com.sinch.sdk.domains.numbers.adapters.converters.CallbackPayloadDtoConverter;
+import com.sinch.sdk.domains.numbers.models.dto.v1.CallbackPayloadDto;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+
+public class WebHooksService implements com.sinch.sdk.domains.numbers.WebHooksService {
+
+ @Override
+ public EventNotification unserializeEventNotification(String jsonPayload)
+ throws ApiMappingException {
+
+ try {
+ CallbackPayloadDto dto =
+ Mapper.getInstance().readValue(jsonPayload, CallbackPayloadDto.class);
+ if (null != dto) {
+ return CallbackPayloadDtoConverter.convert(dto);
+ }
+ throw new ApiMappingException(jsonPayload, null);
+
+ } catch (JsonProcessingException e) {
+ throw new ApiMappingException(jsonPayload, e);
+ }
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/ActiveNumberUpdateRequestParametersDtoConverter.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/ActiveNumberUpdateRequestParametersDtoConverter.java
index 8cca6abb..82e78725 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/ActiveNumberUpdateRequestParametersDtoConverter.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/ActiveNumberUpdateRequestParametersDtoConverter.java
@@ -15,9 +15,7 @@ public static ActiveNumberRequestDto convert(ActiveNumberUpdateRequestParameters
.getVoiceConfiguration()
.ifPresent(
value -> dto.setVoiceConfiguration(VoiceConfigurationDtoConverter.convert(value)));
- // TODO: OAS file do not yet contains callback field
- // parameters.getCallback()
- // .ifPresent(value -> dto.setCallback.convert(value)));
+ parameters.getCallback().ifPresent(dto::setCallbackUrl);
return dto;
}
}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverter.java b/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverter.java
new file mode 100644
index 00000000..388db4d5
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverter.java
@@ -0,0 +1,41 @@
+package com.sinch.sdk.domains.numbers.adapters.converters;
+
+import com.sinch.sdk.domains.numbers.models.SmsErrorCode;
+import com.sinch.sdk.domains.numbers.models.dto.v1.CallbackPayloadDto;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventStatus;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventType;
+import com.sinch.sdk.domains.numbers.models.webhooks.ResourceType;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+
+public class CallbackPayloadDtoConverter {
+
+ public static EventNotification convert(CallbackPayloadDto dto) {
+
+ String eventId = dto.getEventId();
+ // FIXME: Currently Numbers API return a string without timezone
+ // Workaround:
+ // 1. try to parse as ISO8601 (with timezone)
+ // 2. If failure: fallback to a local date time in UTC time zoe
+ Instant timestamp = null;
+ if (null != dto.getTimestamp()) {
+ try {
+ timestamp = Instant.parse(dto.getTimestamp());
+ } catch (DateTimeParseException e) {
+ timestamp = LocalDateTime.parse(dto.getTimestamp()).toInstant(ZoneOffset.UTC);
+ }
+ }
+ String projectId = dto.getProjectId();
+ String resourceId = dto.getResourceId();
+ ResourceType resourceType = ResourceType.from(dto.getResourceType());
+ EventType eventType = EventType.from(dto.getEventType());
+ EventStatus status = EventStatus.from(dto.getStatus());
+ SmsErrorCode failureCode = SmsErrorCode.from(dto.getFailureCode());
+
+ return new EventNotification(
+ eventId, timestamp, projectId, resourceId, resourceType, eventType, status, failureCode);
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/requests/ActiveNumberUpdateRequestParameters.java b/client/src/main/com/sinch/sdk/domains/numbers/models/requests/ActiveNumberUpdateRequestParameters.java
index 8cc04bdb..956dc75c 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/models/requests/ActiveNumberUpdateRequestParameters.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/requests/ActiveNumberUpdateRequestParameters.java
@@ -16,11 +16,10 @@ public class ActiveNumberUpdateRequestParameters {
private final String callback;
/**
- * @param displayName User supplied name for the phone number
- * @param smsConfiguration The current SMS configuration for this number
- * @param voiceConfiguration The current voice configuration for this number
- * @param callback The callback URL to be called for a rented number's provisioning /
- * deprovisioning operations
+ * @param displayName see {@link #getDisplayName() getDisplayName getter}
+ * @param smsConfiguration see {@link #getSmsConfiguration() getSmsConfiguration getter}
+ * @param voiceConfiguration see {@link #getVoiceConfiguration() getVoiceConfiguration getter}
+ * @param callback see {@link #getCallback() getCallback getter}
*/
public ActiveNumberUpdateRequestParameters(
String displayName,
@@ -33,18 +32,31 @@ public ActiveNumberUpdateRequestParameters(
this.callback = callback;
}
+ /**
+ * @return User supplied name for the phone number
+ */
public Optional getDisplayName() {
return Optional.ofNullable(displayName);
}
+ /**
+ * @return The current SMS configuration for this number
+ */
public Optional getSmsConfiguration() {
return Optional.ofNullable(smsConfiguration);
}
+ /**
+ * @return The current voice configuration for this number
+ */
public Optional getVoiceConfiguration() {
return Optional.ofNullable(voiceConfiguration);
}
+ /**
+ * @return The callback URL to be called for a rented number's provisioning / deprovisioning
+ * operations ({@link com.sinch.sdk.domains.numbers.WebHooksService see WebHooksService})
+ */
public Optional getCallback() {
return Optional.ofNullable(callback);
}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventNotification.java b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventNotification.java
new file mode 100644
index 00000000..3b973281
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventNotification.java
@@ -0,0 +1,108 @@
+package com.sinch.sdk.domains.numbers.models.webhooks;
+
+import com.sinch.sdk.domains.numbers.models.SmsErrorCode;
+import java.time.Instant;
+
+/** A notification of an event sent to your configured callback URL. */
+public class EventNotification {
+
+ private final String eventId;
+ private final Instant timestamp;
+ private final String projectId;
+ private final String resourceId;
+ private final ResourceType resourceType;
+ private final EventType eventType;
+ private final EventStatus status;
+ private final SmsErrorCode failureCode;
+
+ /**
+ * @param eventId The ID of the event
+ * @param timestamp The date and time when the callback was created and added to the callbacks
+ * queue
+ * @param projectId The ID of the project to which the event belongs
+ * @param resourceId The unique identifier of the resource, depending on the resource type. For
+ * example, a phone number, a hosting order ID, or a brand ID.
+ * @param resourceType The type of the resource
+ * @param eventType The type of the event
+ * @param status The status of the event
+ * @param failureCode If the status is FAILED, a failure code will be provided. For numbers
+ * provisioning to SMS platform, there won't be any extra failureCode, as the result is
+ * binary. For campaign provisioning-related failures, refer to the list for the possible
+ * values.
+ */
+ public EventNotification(
+ String eventId,
+ Instant timestamp,
+ String projectId,
+ String resourceId,
+ ResourceType resourceType,
+ EventType eventType,
+ EventStatus status,
+ SmsErrorCode failureCode) {
+ this.eventId = eventId;
+ this.timestamp = timestamp;
+ this.projectId = projectId;
+ this.resourceId = resourceId;
+ this.resourceType = resourceType;
+ this.eventType = eventType;
+ this.status = status;
+ this.failureCode = failureCode;
+ }
+
+ public String getEventId() {
+ return eventId;
+ }
+
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+
+ public String getProjectId() {
+ return projectId;
+ }
+
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public ResourceType getResourceType() {
+ return resourceType;
+ }
+
+ public EventType getEventType() {
+ return eventType;
+ }
+
+ public EventStatus getStatus() {
+ return status;
+ }
+
+ public SmsErrorCode getFailureCode() {
+ return failureCode;
+ }
+
+ @Override
+ public String toString() {
+ return "EventNotification{"
+ + "eventId='"
+ + eventId
+ + '\''
+ + ", timestamp="
+ + timestamp
+ + ", projectId='"
+ + projectId
+ + '\''
+ + ", resourceId='"
+ + resourceId
+ + '\''
+ + ", resourceType="
+ + resourceType
+ + ", eventType="
+ + eventType
+ + ", status="
+ + status
+ + ", failureCode="
+ + failureCode
+ + '}';
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventStatus.java b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventStatus.java
new file mode 100644
index 00000000..e0131e5b
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventStatus.java
@@ -0,0 +1,38 @@
+package com.sinch.sdk.domains.numbers.models.webhooks;
+
+import com.sinch.sdk.core.utils.EnumDynamic;
+import com.sinch.sdk.core.utils.EnumSupportDynamic;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+public final class EventStatus extends EnumDynamic {
+
+ /**
+ * @see https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/#tag/Callbacks/operation/ImportedNumberService_EventsCallback!path=status&t=request/
+ * @since 1.0
+ */
+ public static final EventStatus SUCCEEDED = new EventStatus("SUCCEEDED");
+
+ public static final EventStatus FAILED = new EventStatus("FAILED");
+
+ private static final EnumSupportDynamic ENUM_SUPPORT =
+ new EnumSupportDynamic<>(
+ EventStatus.class, EventStatus::new, Arrays.asList(SUCCEEDED, FAILED));
+
+ EventStatus(String value) {
+ super(value);
+ }
+
+ public static Stream values() {
+ return ENUM_SUPPORT.values();
+ }
+
+ public static EventStatus from(String value) {
+ return ENUM_SUPPORT.from(value);
+ }
+
+ public static String valueOf(EventStatus e) {
+ return ENUM_SUPPORT.valueOf(e);
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventType.java b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventType.java
new file mode 100644
index 00000000..d73fb9d0
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/EventType.java
@@ -0,0 +1,66 @@
+package com.sinch.sdk.domains.numbers.models.webhooks;
+
+import com.sinch.sdk.core.utils.EnumDynamic;
+import com.sinch.sdk.core.utils.EnumSupportDynamic;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+/**
+ * @see https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/#tag/Callbacks/operation/ImportedNumberService_EventsCallback!path=eventType&t=request
+ * @since 1.0
+ */
+public final class EventType extends EnumDynamic {
+
+ /** An event that occurs when a number is linked to a Service Plan ID. */
+ public static final EventType PROVISIONING_TO_SMS_PLATFORM =
+ new EventType("PROVISIONING_TO_SMS_PLATFORM");
+
+ /** An event that occurs when a number is unlinked from a Service Plan ID */
+ public static final EventType DEPROVISIONING_FROM_SMS_PLATFORM =
+ new EventType("DEPROVISIONING_FROM_SMS_PLATFORM");
+
+ /** An event that occurs when a number is linked to a Campaign */
+ public static final EventType PROVISIONING_TO_CAMPAIGN =
+ new EventType("PROVISIONING_TO_CAMPAIGN");
+
+ /** An event that occurs when a number is unlinked from a Campaign */
+ public static final EventType DEPROVISIONING_FROM_CAMPAIGN =
+ new EventType("DEPROVISIONING_FROM_CAMPAIGN");
+
+ /** An event that occurs when a number is enabled for Voice operations. */
+ public static final EventType PROVISIONING_TO_VOICE_PLATFORM =
+ new EventType("PROVISIONING_TO_VOICE_PLATFORM");
+
+ /** An event that occurs when a number is disabled for Voice operations */
+ public static final EventType DEPROVISIONING_TO_VOICE_PLATFORM =
+ new EventType("DEPROVISIONING_TO_VOICE_PLATFORM");
+
+ private static final EnumSupportDynamic ENUM_SUPPORT =
+ new EnumSupportDynamic<>(
+ EventType.class,
+ EventType::new,
+ Arrays.asList(
+ PROVISIONING_TO_SMS_PLATFORM,
+ DEPROVISIONING_FROM_SMS_PLATFORM,
+ PROVISIONING_TO_CAMPAIGN,
+ DEPROVISIONING_FROM_CAMPAIGN,
+ PROVISIONING_TO_VOICE_PLATFORM,
+ DEPROVISIONING_TO_VOICE_PLATFORM));
+
+ EventType(String value) {
+ super(value);
+ }
+
+ public static Stream values() {
+ return ENUM_SUPPORT.values();
+ }
+
+ public static EventType from(String value) {
+ return ENUM_SUPPORT.from(value);
+ }
+
+ public static String valueOf(EventType e) {
+ return ENUM_SUPPORT.valueOf(e);
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/ResourceType.java b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/ResourceType.java
new file mode 100644
index 00000000..5b74448f
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/ResourceType.java
@@ -0,0 +1,36 @@
+package com.sinch.sdk.domains.numbers.models.webhooks;
+
+import com.sinch.sdk.core.utils.EnumDynamic;
+import com.sinch.sdk.core.utils.EnumSupportDynamic;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+/**
+ * @see https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Callbacks/#tag/Callbacks/operation/ImportedNumberService_EventsCallback!path=resourceType&t=request
+ * @since 1.0
+ */
+public final class ResourceType extends EnumDynamic {
+
+ /** Numbers which are already active and updated with new campaign IDs or service plan IDs. */
+ public static final ResourceType ACTIVE_NUMBER = new ResourceType("ACTIVE_NUMBER");
+
+ private static final EnumSupportDynamic ENUM_SUPPORT =
+ new EnumSupportDynamic<>(ResourceType.class, ResourceType::new, Arrays.asList(ACTIVE_NUMBER));
+
+ ResourceType(String value) {
+ super(value);
+ }
+
+ public static Stream values() {
+ return ENUM_SUPPORT.values();
+ }
+
+ public static ResourceType from(String value) {
+ return ENUM_SUPPORT.from(value);
+ }
+
+ public static String valueOf(ResourceType e) {
+ return ENUM_SUPPORT.valueOf(e);
+ }
+}
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/package-info.java b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/package-info.java
new file mode 100644
index 00000000..cb0c2db7
--- /dev/null
+++ b/client/src/main/com/sinch/sdk/domains/numbers/models/webhooks/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Numbers API webhooks (callback) related models
+ *
+ * @since 1.0
+ */
+package com.sinch.sdk.domains.numbers.models.webhooks;
diff --git a/client/src/main/com/sinch/sdk/domains/numbers/package-info.java b/client/src/main/com/sinch/sdk/domains/numbers/package-info.java
index 9944337d..f1c82a0d 100644
--- a/client/src/main/com/sinch/sdk/domains/numbers/package-info.java
+++ b/client/src/main/com/sinch/sdk/domains/numbers/package-info.java
@@ -1,6 +1,11 @@
/**
* Numbers API interface
*
+ * The Numbers API enables you to search for, view, and activate numbers. It's considered a
+ * precursor to other APIs in the Sinch product family. The numbers API can be used in tandem with
+ * any of our APIs that perform messaging or calling. Once you have activated your numbers, you can
+ * then use the various other APIs, such as SMS or Voice, to assign and use those numbers.
+ *
* @since 1.0
*/
package com.sinch.sdk.domains.numbers;
diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/WebhooksServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/WebhooksServiceTest.java
new file mode 100644
index 00000000..d69bca54
--- /dev/null
+++ b/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/WebhooksServiceTest.java
@@ -0,0 +1,52 @@
+package com.sinch.sdk.domains.numbers.adapters;
+
+import com.adelean.inject.resources.junit.jupiter.GivenTextResource;
+import com.adelean.inject.resources.junit.jupiter.TestWithResources;
+import com.sinch.sdk.BaseTest;
+import com.sinch.sdk.SinchClient;
+import com.sinch.sdk.core.exceptions.ApiException;
+import com.sinch.sdk.domains.numbers.WebHooksService;
+import com.sinch.sdk.domains.numbers.adapters.converters.CallbackPayloadDtoConverter;
+import com.sinch.sdk.domains.numbers.models.dto.v1.CallbackPayloadDtoTest;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+import com.sinch.sdk.models.Configuration;
+import java.io.IOException;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@TestWithResources
+public class WebhooksServiceTest extends BaseTest {
+
+ @GivenTextResource("/domains/numbers/v1/CallbackPayloadDto.json")
+ String incomingCallbackPayloadDtoJsonString;
+
+ WebHooksService webHooksService;
+
+ @Test
+ void incomingEventNotification() throws ApiException {
+
+ EventNotification response =
+ webHooksService.unserializeEventNotification(incomingCallbackPayloadDtoJsonString);
+
+ Assertions.assertThat(response).isInstanceOf(EventNotification.class);
+ Assertions.assertThat(response)
+ .usingRecursiveComparison()
+ .isEqualTo(
+ CallbackPayloadDtoConverter.convert(
+ CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto));
+ }
+
+ @BeforeEach
+ public void setUp() throws IOException {
+
+ Configuration configuration =
+ Configuration.builder()
+ .setProjectId("unused")
+ .setKeyId("unused")
+ .setKeySecret("unused")
+ .build();
+
+ webHooksService = new SinchClient(configuration).numbers().webhooks();
+ }
+}
diff --git a/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverterTest.java b/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverterTest.java
new file mode 100644
index 00000000..8f4f6579
--- /dev/null
+++ b/client/src/test/java/com/sinch/sdk/domains/numbers/adapters/converters/CallbackPayloadDtoConverterTest.java
@@ -0,0 +1,70 @@
+package com.sinch.sdk.domains.numbers.adapters.converters;
+
+import com.sinch.sdk.domains.numbers.models.SmsErrorCode;
+import com.sinch.sdk.domains.numbers.models.dto.v1.CallbackPayloadDto;
+import com.sinch.sdk.domains.numbers.models.dto.v1.CallbackPayloadDtoTest;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventStatus;
+import com.sinch.sdk.domains.numbers.models.webhooks.EventType;
+import com.sinch.sdk.domains.numbers.models.webhooks.ResourceType;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class CallbackPayloadDtoConverterTest {
+
+ @Test
+ void convertCallbackPayloadDtoWithLocalDate() {
+
+ EventNotification client =
+ new EventNotification(
+ "abcd1234efghijklmnop567890",
+ LocalDateTime.parse("2023-06-06T07:45:27.78789").toInstant(ZoneOffset.UTC),
+ "abcd12ef-ab12-ab12-bc34-abcdef123456",
+ "+12345612345",
+ ResourceType.ACTIVE_NUMBER,
+ EventType.PROVISIONING_TO_CAMPAIGN,
+ EventStatus.FAILED,
+ SmsErrorCode.CAMPAIGN_NOT_AVAILABLE);
+
+ Assertions.assertThat(
+ CallbackPayloadDtoConverter.convert(
+ CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto))
+ .usingRecursiveComparison()
+ .isEqualTo(client);
+ }
+
+ @Test
+ void convertCallbackPayloadDtoWithTimeZone() {
+
+ // patch timestamp with a timezone compliant value
+ CallbackPayloadDto dto =
+ new CallbackPayloadDto()
+ .eventId(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getEventId())
+ // adding a time zone
+ .timestamp(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getTimestamp() + "Z")
+ .projectId(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getProjectId())
+ .resourceId(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getResourceId())
+ .resourceType(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getResourceType())
+ .eventType(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getEventType())
+ .status(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getStatus())
+ .failureCode(CallbackPayloadDtoTest.expectedCallbackPayloadDtoDto.getFailureCode());
+
+ EventNotification client =
+ new EventNotification(
+ "abcd1234efghijklmnop567890",
+ Instant.parse("2023-06-06T07:45:27.78789Z"),
+ "abcd12ef-ab12-ab12-bc34-abcdef123456",
+ "+12345612345",
+ ResourceType.ACTIVE_NUMBER,
+ EventType.PROVISIONING_TO_CAMPAIGN,
+ EventStatus.FAILED,
+ SmsErrorCode.CAMPAIGN_NOT_AVAILABLE);
+
+ Assertions.assertThat(CallbackPayloadDtoConverter.convert(dto))
+ .usingRecursiveComparison()
+ .isEqualTo(client);
+ }
+}
diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/dto/v1/CallbackPayloadDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/dto/v1/CallbackPayloadDtoTest.java
new file mode 100644
index 00000000..72ef298f
--- /dev/null
+++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/numbers/models/dto/v1/CallbackPayloadDtoTest.java
@@ -0,0 +1,32 @@
+package com.sinch.sdk.domains.numbers.models.dto.v1;
+
+import com.adelean.inject.resources.junit.jupiter.GivenJsonResource;
+import com.adelean.inject.resources.junit.jupiter.TestWithResources;
+import com.sinch.sdk.BaseTest;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@TestWithResources
+public class CallbackPayloadDtoTest extends BaseTest {
+
+ @GivenJsonResource("/domains/numbers/v1/CallbackPayloadDto.json")
+ CallbackPayloadDto loadedCallbackPayloadDto;
+
+ public static CallbackPayloadDto expectedCallbackPayloadDtoDto =
+ new CallbackPayloadDto()
+ .eventId("abcd1234efghijklmnop567890")
+ .timestamp("2023-06-06T07:45:27.78789")
+ .projectId("abcd12ef-ab12-ab12-bc34-abcdef123456")
+ .resourceId("+12345612345")
+ .resourceType("ACTIVE_NUMBER")
+ .eventType("PROVISIONING_TO_CAMPAIGN")
+ .status("FAILED")
+ .failureCode("CAMPAIGN_NOT_AVAILABLE");
+
+ @Test
+ void deserializeCallbackPayload() {
+ Assertions.assertThat(loadedCallbackPayloadDto)
+ .usingRecursiveComparison()
+ .isEqualTo(expectedCallbackPayloadDtoDto);
+ }
+}
diff --git a/sample-app/README.md b/sample-app/README.md
index bc82093b..cb78fe20 100644
--- a/sample-app/README.md
+++ b/sample-app/README.md
@@ -116,8 +116,8 @@ A full application chaining calls to Numbers service to onboard onto Java SDK an
| | - GetByReference | [com.sinch.sample.verification.status.GetByReference](src/main/java/com/sinch/sample/verification/status/GetByReference.java) | |
### Dedicated webhooks feature samples
-#### How to run webhooks samples
-Webhooks samples are based onto dedicated SpringBoot applications.
+#### How to run webhooks sample application
+Webhooks samples are based onto a dedicated SpringBoot applications.
By using service like `ngrok` and running locally the SpringBoot application you'll be able to use the local springboot application to response to callbacks defined within your dashboard
1. Install `ngrok` and launch it (see [ngrok site](https://ngrok.com/docs))
2. Run the application: `mvn -f pom-webhooks.xml clean package spring-boot:run`
@@ -130,10 +130,12 @@ By using service like `ngrok` and running locally the SpringBoot application you
#### Verification WebHooks
Require to set following parameters (by environment or config file):
- `APPLICATION_API_KEY`
--` APPLICATION_API_SECRET`
+- `APPLICATION_API_SECRET`
-Check your dashboard to retrieve Application Credentials values
+Check your dashboard to retrieve Application credentials values
+
+| API | Package | Notes |
+|--------------|------------------------------------------------------------------------------------------------|-------|
+| Numbers | [com.sinch.sample.webhooks.numbers](src/main/java/com/sinch/sample/webhooks/numbers) | |
+| Verification | [com.sinch.sample.webhooks.verification](src/main/java/com/sinch/sample/webhooks/verification) | |
-| API | Sample | Class | Notes |
-|--------------|------------------------|---------------------------------------------------------------------------------------------------------------------------|-------|
-| Verification | Springboot application | [com.sinch.sample.webhooks.VerificationApplication](src/main/java/com/sinch/sample/webhooks/VerificationApplication.java) | |
diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java b/sample-app/src/main/java/com/sinch/sample/webhooks/WebHooksApplication.java
similarity index 74%
rename from sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java
rename to sample-app/src/main/java/com/sinch/sample/webhooks/WebHooksApplication.java
index 00511611..f958377f 100644
--- a/sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java
+++ b/sample-app/src/main/java/com/sinch/sample/webhooks/WebHooksApplication.java
@@ -9,12 +9,12 @@
import org.springframework.context.annotation.Bean;
@SpringBootApplication
-public class VerificationApplication {
- private static final Logger LOGGER = Logger.getLogger(VerificationApplication.class.getName());
+public class WebHooksApplication {
+ private static final Logger LOGGER = Logger.getLogger(WebHooksApplication.class.getName());
public static void main(String[] args) {
- SpringApplication.run(VerificationApplication.class, args);
+ SpringApplication.run(WebHooksApplication.class, args);
}
@Bean
diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersController.java b/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersController.java
new file mode 100644
index 00000000..c9739a7a
--- /dev/null
+++ b/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersController.java
@@ -0,0 +1,41 @@
+package com.sinch.sample.webhooks.numbers;
+
+import com.sinch.sdk.SinchClient;
+import java.util.Map;
+import java.util.logging.Logger;
+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.RequestHeader;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class NumbersController {
+
+ private final SinchClient sinchClient;
+ private final NumbersService service;
+ private static final Logger LOGGER = Logger.getLogger(NumbersController.class.getName());
+
+ @Autowired
+ public NumbersController(SinchClient sinchClient, NumbersService service) {
+ this.sinchClient = sinchClient;
+ this.service = service;
+ }
+
+ @PostMapping(
+ value = "/NumbersEvent",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public void NumbersEvent(@RequestHeader Map headers, @RequestBody String body) {
+
+ LOGGER.finest("Received body:" + body);
+ LOGGER.finest("Received headers: " + headers);
+
+ // decode the request payload
+ var event = sinchClient.numbers().webhooks().unserializeEventNotification(body);
+
+ // let business layer process the request
+ service.numbersEvent(event);
+ }
+}
diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersService.java b/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersService.java
new file mode 100644
index 00000000..c85c91cd
--- /dev/null
+++ b/sample-app/src/main/java/com/sinch/sample/webhooks/numbers/NumbersService.java
@@ -0,0 +1,21 @@
+package com.sinch.sample.webhooks.numbers;
+
+import com.sinch.sdk.domains.numbers.models.webhooks.EventNotification;
+import java.util.logging.Logger;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NumbersService {
+
+ private static final Logger LOGGER = Logger.getLogger(NumbersService.class.getName());
+
+ /* @Autowired
+ public NumbersService(SinchClient sinchClient) {
+
+ }*/
+
+ public void numbersEvent(EventNotification event) {
+
+ LOGGER.info("Handle event :" + event);
+ }
+}
diff --git a/sample-app/src/main/resources/application.properties b/sample-app/src/main/resources/application.properties
new file mode 100644
index 00000000..b82d8750
--- /dev/null
+++ b/sample-app/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+# springboot sample related config file
+logging.level.com = TRACE
diff --git a/test-resources/src/test/resources/domains/numbers/v1/CallbackPayloadDto.json b/test-resources/src/test/resources/domains/numbers/v1/CallbackPayloadDto.json
new file mode 100644
index 00000000..55ce3356
--- /dev/null
+++ b/test-resources/src/test/resources/domains/numbers/v1/CallbackPayloadDto.json
@@ -0,0 +1,10 @@
+{
+ "eventId": "abcd1234efghijklmnop567890",
+ "timestamp": "2023-06-06T07:45:27.78789",
+ "projectId": "abcd12ef-ab12-ab12-bc34-abcdef123456",
+ "resourceId": "+12345612345",
+ "resourceType": "ACTIVE_NUMBER",
+ "eventType": "PROVISIONING_TO_CAMPAIGN",
+ "status": "FAILED",
+ "failureCode": "CAMPAIGN_NOT_AVAILABLE"
+}
\ No newline at end of file