From 3f1997f2ff5eed37891692f41deca5a7fdde96a3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 19:45:56 +0000 Subject: [PATCH] Fix NumberFormatException in Webhook.Signature.getTimestamp Webhook.Signature.getTimestamp() calls Long.parseLong() on the raw timestamp value from the signature header without any error handling. If a malformed webhook header arrives with a non-numeric timestamp (e.g. t=abc,v1=sig...), the SDK throws an unhandled NumberFormatException instead of a proper SignatureVerificationException. This change wraps the Long.parseLong() call in a try-catch that returns -1 on NumberFormatException, which the caller (verifyHeader) already handles by throwing a SignatureVerificationException with a descriptive message. Added tests for: - Non-numeric timestamp values - Empty timestamp values - Overflow timestamp values (exceeding Long.MAX_VALUE) Fixes stripe#2149 Co-Authored-By: Jason Kelley --- src/main/java/com/stripe/net/Webhook.java | 6 ++- src/test/java/com/stripe/net/WebhookTest.java | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/stripe/net/Webhook.java b/src/main/java/com/stripe/net/Webhook.java index 09505f70873..eb33976c4f5 100644 --- a/src/main/java/com/stripe/net/Webhook.java +++ b/src/main/java/com/stripe/net/Webhook.java @@ -183,7 +183,11 @@ private static long getTimestamp(String sigHeader) { for (String item : items) { String[] itemParts = item.split("=", 2); if (itemParts[0].equals("t")) { - return Long.parseLong(itemParts[1]); + try { + return Long.parseLong(itemParts[1]); + } catch (NumberFormatException e) { + return -1; + } } } diff --git a/src/test/java/com/stripe/net/WebhookTest.java b/src/test/java/com/stripe/net/WebhookTest.java index 3e1357b6bf3..15c6bd4f24b 100644 --- a/src/test/java/com/stripe/net/WebhookTest.java +++ b/src/test/java/com/stripe/net/WebhookTest.java @@ -160,6 +160,45 @@ public void testMalformedHeader() throws SignatureVerificationException { assertEquals("Unable to extract timestamp and signatures from header", exception.getMessage()); } + @Test + public void testNonNumericTimestamp() throws SignatureVerificationException { + final String sigHeader = "t=not_a_number,v1=some_signature"; + + Throwable exception = + assertThrows( + SignatureVerificationException.class, + () -> { + Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0, null); + }); + assertEquals("Unable to extract timestamp and signatures from header", exception.getMessage()); + } + + @Test + public void testEmptyTimestampValue() throws SignatureVerificationException { + final String sigHeader = "t=,v1=some_signature"; + + Throwable exception = + assertThrows( + SignatureVerificationException.class, + () -> { + Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0, null); + }); + assertEquals("Unable to extract timestamp and signatures from header", exception.getMessage()); + } + + @Test + public void testOverflowTimestampValue() throws SignatureVerificationException { + final String sigHeader = "t=99999999999999999999,v1=some_signature"; + + Throwable exception = + assertThrows( + SignatureVerificationException.class, + () -> { + Webhook.Signature.verifyHeader(payload, sigHeader, secret, 0, null); + }); + assertEquals("Unable to extract timestamp and signatures from header", exception.getMessage()); + } + @Test public void testNoSignaturesWithExpectedScheme() throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException {