Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.424.0"
".": "0.425.0"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.425.0 (2026-02-06)

Full Changelog: [v0.424.0...v0.425.0](https://github.com/Increase/increase-java/compare/v0.424.0...v0.425.0)

### Features

* **api:** add webhook signature verification ([ede3f4c](https://github.com/Increase/increase-java/commit/ede3f4cc63647edcebf4426df3198a6c2a27eda6))

## 0.424.0 (2026-02-06)

Full Changelog: [v0.423.0...v0.424.0](https://github.com/Increase/increase-java/compare/v0.423.0...v0.424.0)
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.increase.api/increase-java)](https://central.sonatype.com/artifact/com.increase.api/increase-java/0.424.0)
[![javadoc](https://javadoc.io/badge2/com.increase.api/increase-java/0.424.0/javadoc.svg)](https://javadoc.io/doc/com.increase.api/increase-java/0.424.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.increase.api/increase-java)](https://central.sonatype.com/artifact/com.increase.api/increase-java/0.425.0)
[![javadoc](https://javadoc.io/badge2/com.increase.api/increase-java/0.425.0/javadoc.svg)](https://javadoc.io/doc/com.increase.api/increase-java/0.425.0)

<!-- x-release-please-end -->

Expand All @@ -13,7 +13,7 @@ The Increase Java SDK is similar to the Increase Kotlin SDK but with minor diffe

<!-- x-release-please-start-version -->

The REST API documentation can be found on [increase.com](https://increase.com/documentation). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.increase.api/increase-java/0.424.0).
The REST API documentation can be found on [increase.com](https://increase.com/documentation). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.increase.api/increase-java/0.425.0).

<!-- x-release-please-end -->

Expand All @@ -24,7 +24,7 @@ The REST API documentation can be found on [increase.com](https://increase.com/d
### Gradle

```kotlin
implementation("com.increase.api:increase-java:0.424.0")
implementation("com.increase.api:increase-java:0.425.0")
```

### Maven
Expand All @@ -33,7 +33,7 @@ implementation("com.increase.api:increase-java:0.424.0")
<dependency>
<groupId>com.increase.api</groupId>
<artifactId>increase-java</artifactId>
<version>0.424.0</version>
<version>0.425.0</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repositories {

allprojects {
group = "com.increase.api"
version = "0.424.0" // x-release-please-version
version = "0.425.0" // x-release-please-version
}

subprojects {
Expand Down
1 change: 1 addition & 0 deletions increase-java-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
api("com.fasterxml.jackson.core:jackson-core:2.18.2")
api("com.fasterxml.jackson.core:jackson-databind:2.18.2")
api("com.google.errorprone:error_prone_annotations:2.33.0")
api("com.standardwebhooks:standardwebhooks:1.1.0")

implementation("com.fasterxml.jackson.core:jackson-annotations:2.18.2")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// File generated from our OpenAPI spec by Stainless.

package com.increase.api.core

import com.increase.api.core.http.Headers
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull

class UnwrapWebhookParams
private constructor(
private val body: String,
private val headers: Headers?,
private val secret: String?,
) {

/** The raw JSON body of the webhook request. */
fun body(): String = body

/** The headers from the webhook request. */
fun headers(): Optional<Headers> = Optional.ofNullable(headers)

/** The secret used to verify the webhook signature. */
fun secret(): Optional<String> = Optional.ofNullable(secret)

fun toBuilder() = Builder().from(this)

companion object {

/**
* Returns a mutable builder for constructing an instance of [UnwrapWebhookParams].
*
* The following fields are required:
* ```java
* .body()
* ```
*/
@JvmStatic fun builder() = Builder()
}

/** A builder for [UnwrapWebhookParams]. */
class Builder internal constructor() {

private var body: String? = null
private var headers: Headers? = null
private var secret: String? = null

@JvmSynthetic
internal fun from(unwrapWebhookParams: UnwrapWebhookParams) = apply {
body = unwrapWebhookParams.body
headers = unwrapWebhookParams.headers
secret = unwrapWebhookParams.secret
}

/** The raw JSON body of the webhook request. */
fun body(body: String) = apply { this.body = body }

/** The headers from the webhook request. */
fun headers(headers: Headers?) = apply { this.headers = headers }

/** Alias for calling [Builder.headers] with `headers.orElse(null)`. */
fun headers(headers: Optional<Headers>) = headers(headers.getOrNull())

/** The secret used to verify the webhook signature. */
fun secret(secret: String?) = apply { this.secret = secret }

/** Alias for calling [Builder.secret] with `secret.orElse(null)`. */
fun secret(secret: Optional<String>) = secret(secret.getOrNull())

/**
* Returns an immutable instance of [UnwrapWebhookParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
* The following fields are required:
* ```java
* .body()
* ```
*
* @throws IllegalStateException if any required field is unset.
*/
fun build(): UnwrapWebhookParams =
UnwrapWebhookParams(checkRequired("body", body), headers, secret)
}

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}

return other is UnwrapWebhookParams &&
body == other.body &&
headers == other.headers &&
secret == other.secret
}

private val hashCode: Int by lazy { Objects.hash(body, headers, secret) }

override fun hashCode(): Int = hashCode

override fun toString() = "UnwrapWebhookParams{body=$body, headers=$headers, secret=$secret}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.increase.api.errors

class IncreaseWebhookException
@JvmOverloads
constructor(message: String? = null, cause: Throwable? = null) : IncreaseException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ package com.increase.api.services.async

import com.increase.api.core.ClientOptions
import com.increase.api.core.RequestOptions
import com.increase.api.core.UnwrapWebhookParams
import com.increase.api.core.http.HttpResponseFor
import com.increase.api.errors.IncreaseInvalidDataException
import com.increase.api.errors.IncreaseWebhookException
import com.increase.api.models.events.Event
import com.increase.api.models.events.EventListPageAsync
import com.increase.api.models.events.EventListParams
Expand Down Expand Up @@ -85,6 +87,14 @@ interface EventServiceAsync {
*/
fun unwrap(body: String): UnwrapWebhookEvent

/**
* Unwraps a webhook event from its JSON representation.
*
* @throws IncreaseInvalidDataException if the body could not be parsed.
* @throws IncreaseWebhookException if the webhook signature could not be verified
*/
fun unwrap(unwrapParams: UnwrapWebhookParams): UnwrapWebhookEvent

/** A view of [EventServiceAsync] that provides access to raw HTTP responses for each method. */
interface WithRawResponse {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package com.increase.api.services.async

import com.increase.api.core.ClientOptions
import com.increase.api.core.RequestOptions
import com.increase.api.core.UnwrapWebhookParams
import com.increase.api.core.checkRequired
import com.increase.api.core.handlers.errorBodyHandler
import com.increase.api.core.handlers.errorHandler
Expand All @@ -15,7 +16,6 @@ import com.increase.api.core.http.HttpResponse.Handler
import com.increase.api.core.http.HttpResponseFor
import com.increase.api.core.http.parseable
import com.increase.api.core.prepareAsync
import com.increase.api.errors.IncreaseInvalidDataException
import com.increase.api.models.events.Event
import com.increase.api.models.events.EventListPageAsync
import com.increase.api.models.events.EventListPageResponse
Expand Down Expand Up @@ -53,14 +53,12 @@ class EventServiceAsyncImpl internal constructor(private val clientOptions: Clie
// get /events
withRawResponse().list(params, requestOptions).thenApply { it.parse() }

/**
* Unwraps a webhook event from its JSON representation.
*
* @throws IncreaseInvalidDataException if the body could not be parsed.
*/
override fun unwrap(body: String): UnwrapWebhookEvent =
EventServiceImpl(clientOptions).unwrap(body)

override fun unwrap(unwrapParams: UnwrapWebhookParams): UnwrapWebhookEvent =
EventServiceImpl(clientOptions).unwrap(unwrapParams)

class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
EventServiceAsync.WithRawResponse {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package com.increase.api.services.blocking
import com.google.errorprone.annotations.MustBeClosed
import com.increase.api.core.ClientOptions
import com.increase.api.core.RequestOptions
import com.increase.api.core.UnwrapWebhookParams
import com.increase.api.core.http.HttpResponseFor
import com.increase.api.errors.IncreaseInvalidDataException
import com.increase.api.errors.IncreaseWebhookException
import com.increase.api.models.events.Event
import com.increase.api.models.events.EventListPage
import com.increase.api.models.events.EventListParams
Expand Down Expand Up @@ -79,6 +81,14 @@ interface EventService {
*/
fun unwrap(body: String): UnwrapWebhookEvent

/**
* Unwraps a webhook event from its JSON representation.
*
* @throws IncreaseInvalidDataException if the body could not be parsed.
* @throws IncreaseWebhookException if the webhook signature could not be verified
*/
fun unwrap(unwrapParams: UnwrapWebhookParams): UnwrapWebhookEvent

/** A view of [EventService] that provides access to raw HTTP responses for each method. */
interface WithRawResponse {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.increase.api.services.blocking
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.increase.api.core.ClientOptions
import com.increase.api.core.RequestOptions
import com.increase.api.core.UnwrapWebhookParams
import com.increase.api.core.checkRequired
import com.increase.api.core.handlers.errorBodyHandler
import com.increase.api.core.handlers.errorHandler
Expand All @@ -17,12 +18,15 @@ import com.increase.api.core.http.HttpResponseFor
import com.increase.api.core.http.parseable
import com.increase.api.core.prepare
import com.increase.api.errors.IncreaseInvalidDataException
import com.increase.api.errors.IncreaseWebhookException
import com.increase.api.models.events.Event
import com.increase.api.models.events.EventListPage
import com.increase.api.models.events.EventListPageResponse
import com.increase.api.models.events.EventListParams
import com.increase.api.models.events.EventRetrieveParams
import com.increase.api.models.events.UnwrapWebhookEvent
import com.standardwebhooks.Webhook
import com.standardwebhooks.exceptions.WebhookVerificationException
import java.util.function.Consumer
import kotlin.jvm.optionals.getOrNull

Expand All @@ -46,18 +50,35 @@ class EventServiceImpl internal constructor(private val clientOptions: ClientOpt
// get /events
withRawResponse().list(params, requestOptions).parse()

/**
* Unwraps a webhook event from its JSON representation.
*
* @throws IncreaseInvalidDataException if the body could not be parsed.
*/
override fun unwrap(body: String): UnwrapWebhookEvent =
try {
clientOptions.jsonMapper.readValue(body, jacksonTypeRef<UnwrapWebhookEvent>())
} catch (e: Exception) {
throw IncreaseInvalidDataException("Error parsing body", e)
}

override fun unwrap(unwrapParams: UnwrapWebhookParams): UnwrapWebhookEvent {
val headers = unwrapParams.headers().getOrNull()
if (headers != null) {
try {
val webhookSecret =
checkRequired(
"webhookSecret",
unwrapParams.secret().getOrNull()
?: clientOptions.webhookSecret().getOrNull(),
)
val headersMap =
headers.names().associateWith { name -> headers.values(name) }.toMap()

val webhook = Webhook(webhookSecret)
webhook.verify(unwrapParams.body(), headersMap)
} catch (e: WebhookVerificationException) {
throw IncreaseWebhookException("Could not verify webhook event signature", e)
}
}
return unwrap(unwrapParams.body())
}

class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
EventService.WithRawResponse {

Expand Down
Loading
Loading