[New Plugin] Webhook Sentinel (Stripe) šŸ”

Hey everyone,

One of the most commonly used integrations in Bubble apps is Stripe, and I’m almost certain that dozens of them out there are configured incorrectly and vulnerable to attacks. This is largely due to the absence of the main verification layer Stripe highly recommends, signature verification, which has never been natively available in Bubble.

If you’ve built a Stripe integration in Bubble, you’ve probably run into this: there’s no native way to verify that an incoming webhook request actually came from Stripe. The community has been working around this for years using approaches as in the list below, with the exception of Signature Verification.

Now, all the approaches in the list (and some others less common, not listed here) offer some level of protection, but none of them replace the signature verification. The community has been asking for a proper solution for years. So after using it privately in my own projects, I decided to publish it as commercial plugin.

The plugin verifies the Stripe-Signature header, checks the timestamp to prevent replay attacks, and returns a clean result with a structured rejection reason for every failure path. If the request is invalid, you can terminate the workflow, log the rejection reason, or handle it however fits your architecture. But the main point is being able to know whether the event is valid or not and decide what to do with this information.

Editor
Preview / Documentation
Plugin Page


Key Features

Stripe - Verify Webhook Signature
:white_check_mark: Cryptographic HMAC-SHA256 signature verification (the same method used by Stripe’s official SDKs)
:white_check_mark: Timestamp freshness check to prevent replay attacks
:white_check_mark: Configurable tolerance window with a safe minimum floor
:white_check_mark: Structured rejection reasons for every failure path: missing_inputs, malformed_header, signature_mismatch, timestamp_expired
:white_check_mark: Returns is valid (yes/no) whether the event passed all verification checks and can be trusted
:white_check_mark: Returns reject reason (text) when invalid for every failure path: missing_inputs, malformed_header, signature_mismatch, timestamp_expired, helping with internal logging and conditional workflow logic.
:white_check_mark: Verify only. The action never stores data, creates records, or produces side effects


Additional context regarding webhook security when integrating with Stripe.

Webhook Security Approaches (Stripe + Bubble)

Summary

Approach Defense Type Key Limitation
Signature Verification :white_check_mark: Primary defense Not natively available in Bubble
Secret key in the URL :warning: Security through obscurity URL stored in plain text in Stripe
API token in the URL :warning: Security through obscurity Same exposure, but compromises entire app
IP Allowlisting :blue_circle: Defense in depth IPs can be spoofed. Not sufficient alone
Event Reconciliation :blue_circle: Complementary Confirms event.id exists, not payload integrity
Timestamp Validation :blue_circle: Complementary Does not prove payload origin or integrity
Event Idempotency :blue_circle: Complementary Protects against retries, not attacks
Environment Isolation :blue_circle: Complementary Protects against legitimate undesired behavior
Minimum Data Validation :blue_circle: Complementary Protects against legitimate undesired behavior

Details of each approach

Signature Verification

  • Type: Primary defense (the only approach with cryptographic guarantee)
  • How it works: Computes the expected signature using the stripe signing secret and compares it against the value received in the Stripe-Signature header. If they match, it proves that the request came from Stripe and that the payload was not modified in transit. Any modification to the payload, even a single character, produces a different signature and the verification fails. No other approach offers this guarantee.
  • Stripe’s recommendation: This is the primary security layer officially recommended by Stripe for all webhook endpoints.

Secret key in the URL

?secret_key=YourSecretKey

  • Type: Security through obscurity
  • How it works: Adds a secret key value to the endpoint URL and verifies internally in the workflow whether the value in the request matches the expected value.
  • Vulnerability: The URL may be stored in plain text in Stripe. Anyone with access to the account can see and use the secret key directly, and if the request is intercepted or the Stripe account is compromised, the secret key will be too.
  • Limitation: Can compromise any endpoints that use the same secret as an internal conditional check.

API token in the URL

?api_token=YourAPIToken

  • Type: Security through obscurity (with amplified risk)
  • How it works: Bubble apparently allows (or allowed in the past) authenticating workflow API calls by passing an API token in the URL using the specific key api_token. The verification was automatic, allowing the workflow to execute with administrator privileges.
  • Vulnerability: Same exposure as the secret key in the URL approach. The URL may be stored in plain text in Stripe and can be intercepted or compromised along with the api_token.
  • Limitation: Significantly greater impact. The token is not limited to the webhook, anyone who has it can execute other endpoints and actions in the app with Admin privileges.

Event Reconciliation

  • Type: Complementary layer
  • How it works: Confirms that the received event.id actually exists in the Stripe account via an API call.
  • Vulnerability: Only confirms that the event.id is real, not that the rest of the payload was not modified in transit.
  • Limitation: An attacker with access to a legitimate event.id could still manipulate other fields in the payload.

Timestamp Validation (recency verification)

  • Type: Complementary layer
  • How it works: Verifies the age of the event by comparing a timestamp against the current time, rejecting events considered too old. There are two ways to do this, each with different behaviors.

Using the event timestamp (payload body)
This timestamp represents when the event was originally generated in Stripe, not when it was delivered. Therefore, it can be forged in a fake request since it is part of the payload body. Additionally, it is not regenerated on retries, so a legitimate event delivered via retry may be incorrectly rejected for appearing old, even though it is a valid delivery.

Using the signature timestamp (Stripe-Signature header)
This timestamp represents when that specific delivery was signed by Stripe and is regenerated on every delivery, each retry has a new timestamp reflecting the moment of that attempt. It is also part of the signature and therefore cannot be forged without invalidating it, as long as signature verification is implemented.

  • Limitation: Regardless of the approach, timestamp validation alone does not prove that the payload came from Stripe or that it was not modified in transit.

IP Allowlisting

  • Type: Defense in depth (complementary, not primary)
  • How it works: Restricts the endpoint to accept only requests originating from IPs published by Stripe.
  • Vulnerability: IPs can be spoofed. The list published by Stripe may change, requiring active maintenance.
  • Limitation: Not sufficient on its own, does not verify payload authenticity, data integrity, or event validity.

Event Idempotency

  • Type: Complementary layer (essential regardless of signature verification)
  • How it works: Uses event.id as a unique key to ensure the same event is not processed more than once. Signature verification proves the request came from Stripe, but Stripe may deliver the same event more than once via retry. Idempotency protects against this legitimate behavior, not against attacks.
  • Limitation: Only protects against duplicates of the same event.id, different events pointing to the same object require additional protection via business idempotency.

Environment Isolation

  • Type: Complementary layer (protection against undesired legitimate behavior)
  • How it works: Compares the event environment (livemode) with the current system environment, rejecting test events in production and vice versa. Signature verification does not distinguish environments, a valid signed test event would pass verification normally. This layer prevents test data from contaminating production.

Minimum Data Validation

  • Type: Complementary layer (protection against undesired legitimate behavior)
  • How it works: Verifies that the event contains the minimum identifiers required to continue processing. An authentic signed event may arrive with missing or unexpected fields. Checking for minimum data before proceeding prevents silent errors in the workflow.

Of course, many of you may already have some of these approaches implemented, and if not, implementing at least one is a good starting point. Each layer adds value, and having multiple in place is always better than relying on just one.

That said, there’s something worth addressing directly. Since signature verification isn’t a native Bubble feature, using this plugin means trusting a third-party service, the plugin creator, and I completely understand any concerns about this. I would worry about that too. In any case, I’d love to hear any feedback, questions, or suggestions, and also help in any way I can.

2 Likes