Stripe webhook signature verification in Apex

Stripe webhook signature verification in Apex

I often need to know the paid subscribers of my blog whenever a premium article is ready to be published, because these articles can be accessed only by paid members of

I am using Stripe as a payment gateway to receive payments and register users as members. The paid and free subscribers info are all available in my blogging platform ( but filtering paid from free and triggering email was all manual effort. Also access to some of the code samples needs to be restricted for members that are paying. To automate this I thought of Salesforce - I now needed the subscription info to flow into Salesforce from Stripe when a user made a payment and successfully subscribed.

I made use of Stripe webhooks

Webhooks are like adding event listeners in JS to one or more events that are triggered on a web page. In this case a user making a payment and subscribing is one of many events. We can therefore configure the events that we are interested in [and want to be notified] to trigger a handler code. The handler code resides in a custom endpoint (Apex REST service) and this endpoint address is configured as a listener a.k.a. webhook.

Stripe API reference – List all events – curl
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.

Sample webhook configuration for charge.succeeded event

Metaphorically, webhooks are like a phone number that Stripe calls to notify you of activity in your Stripe account. The activity could be the creation of a new customer or the payout of funds to your bank account. Source

Build the Webhook in Salesforce

The first step to adding webhooks to Stripe integration is to build our own custom endpoint. For this we write an Apex REST service that can handle webhook notifications sent as POST requests by Stripe. The endpoint of this service will be of the from https://<your-sf-domain-name>/<path prefix of site if any>/services/apexrest/<url suffix of REST service> . For e.g. below is the service I have created and following is the endpoint. The service class should be provided access to guest user profile via Sites or Communities.

The endpoint should be public because Stripe expects it to be so; no option for oAuth etc. More on security in the next section.

Apex REST Service

global with sharing class StripeService

    global static String processEvent(){
        // code to read request verify signature and process the event

Webhook Endpoint and configuration

Securing the endpoint from unintended application

Since the endpoint is public there should be a way to verify that the incoming requests are indeed coming from Stripe. Stripe can optionally sign the webhook events it sends to the configured endpoints by including a signature in each event’s Stripe-Signature header. This allows us to verify that the events were sent by Stripe, not by a third party. We can verify signatures either using Stripes official libraries, or manually using our own solution. Since there is no official library for Salesforce I have done it by building my own apex library.

In order to verify the signatures, we need to retrieve our endpoint’s secret from the Dashboard’s Webhooks settings. Select the endpoint that we want to obtain the secret for, then click the Click to reveal button.

Stripe generates a unique secret key for each endpoint. After this setup, Stripe starts to sign each webhook it sends to the endpoint. A sample Stripe-Signature looks like below:

Stripe-Signature: t=1492774577, v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd, v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39

Stripe generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, you should ignore all schemes that are not v1.

Verify webhook event signatures in apex

These steps from the docs has been implemented in the apex class. The tricky part is step 2 and 4 where chances are we might make a mistake (atleast I made).

Step 1: Extract the timestamp and signatures from the header

Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). You can discard all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload (i.e., the request body) - No Blob or Base64 encoding. The JSON body string should be taken as it is with special characters like quotes (") and newline escaped.

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature (or signatures) in the header to the calculated signature. The signature passed by Stripe is in HEX and therefore before we compare the incoming and calculated signatures we should either encode the incoming HEX signature to Base64 or encode the calculated signature to HEX.

Stripe Webhook Signature Verification

Test the webhook

Once the endpoint is ready and configured in Stripe we can test the webhook directly from Stripe Dashboard in test mode as shown below

Complete APEX Code

If you are viewing this section in an application's internal browser like LinkedIn you will see the following section as blank. The content will be visible only in usual browser and is available only for subscribers.

Note: The APEX code below is only for signature verification. The actual JSON processing depends on the event you configured in Stripe. You can take the sample JSON payload while testing the webhook for your event and generate wrapper classes.


DZining the building blocks for Enterprise Applications


Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to THR(EA)DZ.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.