Skip to content

Conversation

@xavdid-stripe
Copy link
Member

@xavdid-stripe xavdid-stripe commented Nov 5, 2025

Why?

We've been designing a streamlined approach to handling incoming events that is easy to get right and hard to get wrong. This PR has the initial implementation of this new system.

NOTE: final names for classes / methods are still being gaveled, so don't worry about those for now. Weigh in on this doc if you have strong feelings there!

The only other pending item is to add a method to allow handling a webhook without verifying the signature. This is good for testing and for Event Bridge, which doesn't use the signature-based verification. Otherwise, this is ready for review.

What?

  • add EventHandler class
  • add event handler constructor on StripeClient
  • add tests
  • add method for changing the context of an existing client

Example usage

public class EventNotificationWebhookHandler {
  private static final String API_KEY = System.getenv("STRIPE_API_KEY");
  private static final String WEBHOOK_SECRET = System.getenv("WEBHOOK_SECRET");

  private static final StripeClient client = new StripeClient(API_KEY);
  private static final StripeEventRouter router =
      client.router(
          WEBHOOK_SECRET, EventNotificationWebhookHandler::handleUnhandledEventNotification);

  public static void main(String[] args) throws IOException {
    router.on_V1BillingMeterErrorReportTriggeredEventNotification(
        EventNotificationWebhookHandler::handleMeterErrors);

    HttpServer server = HttpServer.create(new InetSocketAddress(4242), 0);
    server.createContext("/webhook", new WebhookHandler());
    server.setExecutor(null);
    server.start();
  }

  private static void handleUnhandledEventNotification(
      EventNotification notif, StripeClient client, UnhandledNotificationDetails details) {
    System.out.println("Received unhandled event notification type: " + notif.getType());
  }

  private static void handleMeterErrors(
      V1BillingMeterErrorReportTriggeredEventNotification notif, StripeClient client) {
    Meter meter = notif.fetchRelatedObject();
    System.out.println("Handling meter error for meter: " + meter.getDisplayName());
  }

  static class WebhookHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange exchange) throws IOException {
      if ("POST".equals(exchange.getRequestMethod())) {
        InputStream requestBody = exchange.getRequestBody();
        String webhookBody = new String(requestBody.readAllBytes(), StandardCharsets.UTF_8);
        String sigHeader = exchange.getRequestHeaders().getFirst("Stripe-Signature");

        try {
          router.handle(webhookBody, sigHeader);

          exchange.sendResponseHeaders(200, -1);
        } catch (StripeException e) {
          exchange.sendResponseHeaders(400, -1);
        }
      } else {
        exchange.sendResponseHeaders(405, -1);
      }
      exchange.close();
    }
  }
}

See Also

@xavdid-stripe xavdid-stripe changed the base branch from master to beta November 15, 2025 02:05
@xavdid-stripe xavdid-stripe changed the title add "inverted" event handler add event handler class Nov 19, 2025
@xavdid-stripe xavdid-stripe marked this pull request as ready for review November 20, 2025 00:06
@xavdid-stripe xavdid-stripe requested a review from a team as a code owner November 20, 2025 00:06
*/
protected void setContext(String context) {
// TODO(major): add getOptions to the StripeResponseGetter interface? that would simplify this
if (!(responseGetter instanceof LiveStripeResponseGetter)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this and am all ears for a better suggestion!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, not much you can do. Seems like the wrong abstractions were put in place (or they made the common Java mistake of creating an interface when there was only ever going to be a single implementation)

@xavdid-stripe xavdid-stripe removed the request for review from a team November 21, 2025 00:36
Copy link
Contributor

@mbroshi-stripe mbroshi-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't repeat all of my comments from the Go review, but they carry over (concurrency concerns and naming, mostly). Java-specific stuff looks good to me

*/
protected void setContext(String context) {
// TODO(major): add getOptions to the StripeResponseGetter interface? that would simplify this
if (!(responseGetter instanceof LiveStripeResponseGetter)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, not much you can do. Seems like the wrong abstractions were put in place (or they made the common Java mistake of creating an interface when there was only ever going to be a single implementation)

}
}

private boolean hasHandledEvent = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar concurrency concerns as in Go: You probably want to set this to volatile or use an AtomicBoolean

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.

3 participants