diff --git a/gradle.properties b/gradle.properties index 8ed9dd9..7a57b5c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.resend -VERSION_NAME=4.23.0 +VERSION_NAME=4.24.0 POM_URL=https://github.com/resendlabs/resend-java POM_SCM_URL=https://github.com/resendlabs/resend-java.git diff --git a/src/main/java/com/resend/services/webhooks/Webhooks.java b/src/main/java/com/resend/services/webhooks/Webhooks.java index ae37e3a..9bab52f 100644 --- a/src/main/java/com/resend/services/webhooks/Webhooks.java +++ b/src/main/java/com/resend/services/webhooks/Webhooks.java @@ -20,6 +20,7 @@ import com.resend.services.webhooks.model.ListWebhookEventsParams; import com.resend.services.webhooks.model.ListWebhookEventsResponseSuccess; import com.resend.services.webhooks.model.ReplayWebhookEventResponseSuccess; +import com.resend.services.webhooks.model.RotateWebhookSigningSecretResponseSuccess; import com.resend.services.webhooks.model.VerifyWebhookOptions; import okhttp3.MediaType; import javax.crypto.Mac; @@ -204,6 +205,23 @@ public ReplayWebhookEventResponseSuccess replayEvent(String webhookId, String ev return resendMapper.readValue(response.getBody(), ReplayWebhookEventResponseSuccess.class); } + /** + * Rotates the signing secret of a webhook. The previous secret keeps working for 24 hours. + * + * @param webhookId The unique identifier of the webhook. + * @return A RotateWebhookSigningSecretResponseSuccess containing the webhook ID and the new signing secret. + * @throws ResendException If an error occurs while rotating the signing secret. + */ + public RotateWebhookSigningSecretResponseSuccess rotateSigningSecret(String webhookId) throws ResendException { + AbstractHttpResponse response = httpClient.perform("/webhooks/" + webhookId + "/signing-secret/rotate", super.apiKey, HttpMethod.POST, "", MediaType.get("application/json")); + + if (!response.isSuccessful()) { + throw new ResendException(response.getCode(), response.getBody()); + } + + return resendMapper.readValue(response.getBody(), RotateWebhookSigningSecretResponseSuccess.class); + } + /** * Retrieves the delivery attempts for a webhook event. * diff --git a/src/main/java/com/resend/services/webhooks/model/RotateWebhookSigningSecretResponseSuccess.java b/src/main/java/com/resend/services/webhooks/model/RotateWebhookSigningSecretResponseSuccess.java new file mode 100644 index 0000000..b29aa24 --- /dev/null +++ b/src/main/java/com/resend/services/webhooks/model/RotateWebhookSigningSecretResponseSuccess.java @@ -0,0 +1,50 @@ +package com.resend.services.webhooks.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a successful response from rotating a webhook signing secret. + */ +public class RotateWebhookSigningSecretResponseSuccess { + @JsonProperty("object") + private String object; + + @JsonProperty("id") + private String id; + + @JsonProperty("signing_secret") + private String signingSecret; + + /** + * Constructs an empty webhook signing secret rotation response. + */ + public RotateWebhookSigningSecretResponseSuccess() { + } + + /** + * Gets the object type. + * + * @return The object type. + */ + public String getObject() { + return object; + } + + /** + * Gets the webhook ID. + * + * @return The webhook ID. + */ + public String getId() { + return id; + } + + /** + * Gets the new signing secret for webhook verification. + * + * @return The signing secret. + */ + public String getSigningSecret() { + return signingSecret; + } +} diff --git a/src/test/java/com/resend/services/webhooks/WebhooksTest.java b/src/test/java/com/resend/services/webhooks/WebhooksTest.java index 2fb5e71..9ac754f 100644 --- a/src/test/java/com/resend/services/webhooks/WebhooksTest.java +++ b/src/test/java/com/resend/services/webhooks/WebhooksTest.java @@ -60,6 +60,9 @@ public class WebhooksTest { private static final String REPLAY_WEBHOOK_EVENT_JSON = "{\"object\":\"webhook_event\",\"id\":\"" + EVENT_ID + "\"}"; + private static final String ROTATE_WEBHOOK_SIGNING_SECRET_JSON = + "{\"object\":\"webhook\",\"id\":\"" + WEBHOOK_ID + "\",\"signing_secret\":\"whsec_rotated_secret\"}"; + private static final String LIST_WEBHOOK_EVENT_ATTEMPTS_JSON = "{\"object\":\"list\",\"has_more\":false,\"data\":[{\"id\":\"atmpt_1srOrx2ZWZBpBUvZwXKQmoEYga2\",\"http_status_code\":200,\"response\":\"{\\\"ok\\\":true}\",\"sent_at\":\"2026-08-22T15:33:12.000Z\"}]}"; @@ -218,6 +221,20 @@ public void testReplayWebhookEvent_Success() throws ResendException { assertEquals(EVENT_ID, response.getId()); } + @Test + public void testRotateWebhookSigningSecret_Success() throws ResendException { + AbstractHttpResponse httpResponse = new AbstractHttpResponse<>(200, ROTATE_WEBHOOK_SIGNING_SECRET_JSON, true); + + when(httpClient.perform(eq("/webhooks/" + WEBHOOK_ID + "/signing-secret/rotate"), anyString(), eq(HttpMethod.POST), eq(""), any(MediaType.class))) + .thenReturn(httpResponse); + + RotateWebhookSigningSecretResponseSuccess response = webhooks.rotateSigningSecret(WEBHOOK_ID); + + assertEquals("webhook", response.getObject()); + assertEquals(WEBHOOK_ID, response.getId()); + assertEquals("whsec_rotated_secret", response.getSigningSecret()); + } + @Test public void testListWebhookEventAttemptsWithPagination_Success() throws ResendException { AbstractHttpResponse httpResponse = new AbstractHttpResponse<>(200, LIST_WEBHOOK_EVENT_ATTEMPTS_JSON, true);