From d23de0b9a72e37e39a34efd9ed26d60e8bd1fed3 Mon Sep 17 00:00:00 2001 From: voj-tech-j Date: Wed, 22 Apr 2026 15:44:06 +0200 Subject: [PATCH] feat: support renamed url field and namespaced events --- CHANGELOG.md | 15 +++++++ README.md | 4 +- gradle.properties | 2 +- .../webhooks/model/UpdateWebhookOptions.java | 21 +++++++--- .../lettr/services/webhooks/WebhooksTest.java | 40 +++++++++++++++++-- 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f217169..4b7a135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] - 2026-04-22 + +### Added + +- `UpdateWebhookOptions.url()` — matches the renamed API field on `PUT /webhooks/{id}` + +### Changed + +- Webhook event types are now namespaced (e.g. `message.delivery`, `engagement.click`, `generation.generation_failure`). The server emits and accepts the namespaced form on both `POST` and `PUT`. Update any hard-coded event strings in `CreateWebhookOptions.events` / `UpdateWebhookOptions.events`. + +### Deprecated + +- `UpdateWebhookOptions.target()` / `getTarget()` — use `url()` / `getUrl()`. The builder still accepts `target` for source compatibility and serializes it as `url`, so existing callers keep working. + ## [1.0.0] - 2026-04-21 ### Added @@ -57,6 +71,7 @@ Initial release. - Bearer token auth, Gson-based JSON serialization - Structured exceptions: `LettrException`, `LettrApiException`, `LettrValidationException` +[1.1.0]: https://github.com/lettr/lettr-java/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/lettr/lettr-java/compare/v0.2.0...v1.0.0 [0.2.0]: https://github.com/lettr/lettr-java/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/lettr/lettr-java/releases/tag/v0.1.0 diff --git a/README.md b/README.md index 81a60ea..7995286 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ Webhook webhook = lettr.webhooks().create( .authUsername("user") .authPassword("secret") .eventsMode("selected") - .events(List.of("delivery", "bounce")) + .events(List.of("message.delivery", "message.bounce")) .build() ); System.out.println("Webhook ID: " + webhook.getId()); @@ -315,7 +315,7 @@ Webhook webhook = lettr.webhooks().get("webhook-abc123"); Webhook updated = lettr.webhooks().update("webhook-abc123", UpdateWebhookOptions.builder() .name("Updated Webhook") - .target("https://new.example.com/webhook") + .url("https://new.example.com/webhook") .active(false) .build() ); diff --git a/gradle.properties b/gradle.properties index 0827eec..186a9de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.lettr -VERSION=1.0.0 +VERSION=1.1.0 POM_ARTIFACT_ID=lettr-java POM_NAME=Lettr Java SDK POM_DESCRIPTION=Java SDK for the Lettr Email API diff --git a/src/main/java/com/lettr/services/webhooks/model/UpdateWebhookOptions.java b/src/main/java/com/lettr/services/webhooks/model/UpdateWebhookOptions.java index 88e1ac5..05eeb93 100644 --- a/src/main/java/com/lettr/services/webhooks/model/UpdateWebhookOptions.java +++ b/src/main/java/com/lettr/services/webhooks/model/UpdateWebhookOptions.java @@ -13,7 +13,7 @@ public class UpdateWebhookOptions { private final String name; - private final String target; + private final String url; @SerializedName("auth_type") private final String authType; @SerializedName("auth_username") private final String authUsername; @@ -27,7 +27,7 @@ public class UpdateWebhookOptions { private UpdateWebhookOptions(Builder builder) { this.name = builder.name; - this.target = builder.target; + this.url = builder.url; this.authType = builder.authType; this.authUsername = builder.authUsername; this.authPassword = builder.authPassword; @@ -44,7 +44,10 @@ public static Builder builder() { } @Nullable public String getName() { return name; } - @Nullable public String getTarget() { return target; } + @Nullable public String getUrl() { return url; } + /** @deprecated use {@link #getUrl()} — the API field was renamed from {@code target} to {@code url}. */ + @Deprecated + @Nullable public String getTarget() { return url; } @Nullable public String getAuthType() { return authType; } @Nullable public String getAuthUsername() { return authUsername; } @Nullable public String getAuthPassword() { return authPassword; } @@ -56,7 +59,7 @@ public static Builder builder() { public static class Builder { private String name; - private String target; + private String url; private String authType; private String authUsername; private String authPassword; @@ -71,8 +74,14 @@ private Builder() {} /** (optional) Sets the new webhook name. Max length: 255. */ @Nonnull public Builder name(@Nullable String name) { this.name = name; return this; } - /** (optional) Sets the new webhook target URL. Max length: 2048. */ - @Nonnull public Builder target(@Nullable String target) { this.target = target; return this; } + /** (optional) Sets the new webhook URL. Max length: 2048. */ + @Nonnull public Builder url(@Nullable String url) { this.url = url; return this; } + + /** + * @deprecated use {@link #url(String)} — the API field was renamed from {@code target} to {@code url}. + */ + @Deprecated + @Nonnull public Builder target(@Nullable String target) { this.url = target; return this; } /** (optional) Sets the new authentication type. */ @Nonnull public Builder authType(@Nullable String authType) { this.authType = authType; return this; } diff --git a/src/test/java/com/lettr/services/webhooks/WebhooksTest.java b/src/test/java/com/lettr/services/webhooks/WebhooksTest.java index c3bf2e8..23961a6 100644 --- a/src/test/java/com/lettr/services/webhooks/WebhooksTest.java +++ b/src/test/java/com/lettr/services/webhooks/WebhooksTest.java @@ -66,7 +66,7 @@ void createWebhookOptionsBuildsWithAllFields() { .authUsername("user") .authPassword("pass") .eventsMode("selected") - .events(Arrays.asList("delivery", "bounce")) + .events(Arrays.asList("message.delivery", "message.bounce")) .build(); assertEquals("Test Webhook", options.getName()); @@ -108,7 +108,7 @@ void updateWebhookOptionsBuildsWithNoFields() { void updateWebhookOptionsBuildsWithAllFields() { UpdateWebhookOptions options = UpdateWebhookOptions.builder() .name("Updated") - .target("https://new.example.com/webhook") + .url("https://new.example.com/webhook") .authType("oauth2") .oauthClientId("client-id") .oauthClientSecret("secret") @@ -118,11 +118,45 @@ void updateWebhookOptionsBuildsWithAllFields() { .build(); assertEquals("Updated", options.getName()); - assertEquals("https://new.example.com/webhook", options.getTarget()); + assertEquals("https://new.example.com/webhook", options.getUrl()); assertEquals("oauth2", options.getAuthType()); assertEquals(false, options.getActive()); } + @Test + @SuppressWarnings("deprecation") + void updateWebhookOptionsDeprecatedTargetStillWorks() { + UpdateWebhookOptions options = UpdateWebhookOptions.builder() + .target("https://legacy.example.com/webhook") + .build(); + + assertEquals("https://legacy.example.com/webhook", options.getUrl()); + assertEquals("https://legacy.example.com/webhook", options.getTarget()); + } + + @Test + void updateWebhookOptionsSerializesUrlNotTarget() { + UpdateWebhookOptions options = UpdateWebhookOptions.builder() + .url("https://new.example.com/webhook") + .build(); + + String json = gson.toJson(options); + assertTrue(json.contains("\"url\"")); + assertFalse(json.contains("\"target\"")); + } + + @Test + @SuppressWarnings("deprecation") + void updateWebhookOptionsDeprecatedTargetSerializesAsUrl() { + UpdateWebhookOptions options = UpdateWebhookOptions.builder() + .target("https://legacy.example.com/webhook") + .build(); + + String json = gson.toJson(options); + assertTrue(json.contains("\"url\"")); + assertFalse(json.contains("\"target\"")); + } + // --- Webhook deserialization --- @Test