Skip to content

feat: add webhook delivery retry - #66

Merged
bjarn merged 2 commits into
mainfrom
feat/webhook-delivery-retry
Oct 19, 2026
Merged

bjarn merged 2 commits into
mainfrom
feat/webhook-delivery-retry

Conversation

@bjarn

@bjarn bjarn commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

What changed

Adds webhooks().deliveries().retry(webhookId, deliveryId[, RequestOptions]), next to deliveries().retrieve(...). It sends a webhook delivery's original payload to the webhook again now. The retry counts as a delivery attempt; if it fails with a retryable error, automatic retries continue. It needs the team token, sends no request body and takes no Idempotency-Key.

The regenerated types also add nextRetryAt() to WebhookDeliveryData and WebhookDeliveryListData.

Non-breaking. Suggested bump: minor (3.1.0 → 3.2.0).

API changes in the regenerated types

Change Java SDK
New POST /webhooks/{webhookId}/deliveries/{deliveryId}/retry (team token, no body). 202 returns {message, delivery_id}. 409 with WEBHOOK_DISABLED, WEBHOOK_DELIVERY_NOT_RETRYABLE or WEBHOOK_DELIVERY_RETRY_IN_PROGRESS; also 401, 403, 404 and 429 (Retry-After). Operations.RETRY_WEBHOOK_DELIVERY, new RetryWebhookDeliveryResponse record, webhooks().deliveries().retry(...). A 409 is thrown as the existing ConflictException, with getCode() returning the error code.
next_retry_at on WebhookDeliveryData and WebhookDeliveryListData: when the next automatic retry is due, or null when none is scheduled. Optional and nullable. New String nextRetryAt() record component and Builder.nextRetryAt(...) on both records.
payload, response_body, response_headers and error_message on WebhookDeliveryData: the description now says they are always included with a team API token. Javadoc only; the types are unchanged.
The webhook delivery endpoints and regenerate-secret now document 401, 403 and 429 bodies as either {message} or {error: {code, message, ...}}. Error lists in Operations updated. The error parser already handled both shapes; new tests cover both on the retry endpoint.

Every generated file also has a new spec header.

Compatibility note

WebhookDeliveryData and WebhookDeliveryListData are records, so their canonical constructors gain a nextRetryAt argument. Code that calls new WebhookDeliveryData(...) or new WebhookDeliveryListData(...) directly (for example in test fixtures) must add the argument; builders are unaffected. Earlier minor releases shipped the same kind of change, so this is released as a minor.

Usage

RetryWebhookDeliveryResponse retry = lettermint.webhooks().deliveries().retry(webhookId, deliveryId);
System.out.println(retry.message() + " " + retry.deliveryId());

WebhookDeliveryData delivery = lettermint.webhooks().deliveries().retrieve(webhookId, deliveryId);
System.out.println(delivery.nextRetryAt()); // null when no automatic retry is scheduled

try {
    lettermint.webhooks().deliveries().retry(webhookId, deliveryId);
} catch (ConflictException e) {
    System.out.println(e.getCode()); // e.g. WEBHOOK_DELIVERY_NOT_RETRYABLE
}

Checks

  • ./gradlew checkGeneratedTypes: generated files up to date (206 files)
  • ./gradlew clean build --no-daemon on JDK 24: BUILD SUCCESSFUL (tests, javadoc, generated types check)
  • ./gradlew clean build --no-daemon on JDK 17, run as CI runs it: BUILD SUCCESSFUL; 93 tests, 0 failures
  • Build output check from CI: Automatic-Module-Name: co.lettermint present, okhttp not bundled
  • New tests in TeamApiTest:
    • everyOperationIsReachable now calls deliveries().retry and covers all operations
    • retriesAWebhookDelivery: path, POST, team bearer token, no body, no content type, no Idempotency-Key, parsed 202 response, path-parameter validation, missing team token
    • retryingADeliveryMapsErrorsToApiExceptions: 409 WEBHOOK_DELIVERY_NOT_RETRYABLE as ConflictException with its code; 429 with a {message} body plus Retry-After, and with an {error} body; 401; 404
    • webhookDeliveriesExposeTheNextRetryAt: value, null and absent, on retrieve and list

Not run locally

  • JDK 21 and 25, and Windows from the CI matrix (not installed locally).
  • ./gradlew checkstyleMain checkstyleTest: these tasks do not exist in this project (the code-style workflow runs them with || true).

Suggested release tag

v3.2.0. It also covers the unreleased messages resend and per-email batch idempotency keys.

🤖 Generated with Claude Code

bjarn and others added 2 commits October 18, 2026 22:51
- Add the retry webhook delivery operation (POST /webhooks/{webhookId}/deliveries/{deliveryId}/retry) and RetryWebhookDeliveryResponse
- Add next_retry_at to WebhookDeliveryData and WebhookDeliveryListData
- Document that a delivery's payload, response body, response headers and error message are always included with a team API token
- Webhook delivery and regenerate-secret errors (401, 403, 429) can have either an ErrorMessage or an ApiError body

Co-Authored-By: Claude <noreply@anthropic.com>
Add webhooks().deliveries().retry(webhookId, deliveryId[, RequestOptions]),
which sends a delivery's original payload to the webhook again now. It
uses the team token and sends no request body. A 409 (webhook disabled,
delivery not retryable, or a retry already in progress) is thrown as a
ConflictException with the error code.

Co-Authored-By: Claude <noreply@anthropic.com>
@bjarn
bjarn requested a review from a team as a code owner October 18, 2026 20:57
@bjarn
bjarn merged commit 425b24d into main Oct 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants