Webhook Signature Verification

1,248 views
Skip to first unread message

ry...@paidlabs.com

unread,
Jul 11, 2017, 3:22:22 PM7/11/17
to Stripe API Discussion
We are having issues verifying the signature of our webhooks.  We looked at the guide and got the following:

def stripe_event_body
   
@stripe_event_body ||= request.body.read
end


def stripe_event_hash
   
@stripe_event_hash ||= begin
      JSON
.parse(stripe_event_body)
   
rescue
     
nil
   
end
 
end


 
def stripe_header_signature
    request
.env['HTTP_STRIPE_SIGNATURE']
 
end




StripeClient.valid_webhook?(
      body
: stripe_event_body,
      header_signature
: stripe_header_signature,
      payload
: stripe_event_hash
   
)




# stripe_client.rb

def self.valid_webhook?(body:, header_signature:, payload:)
   
# Verify payload
   
begin
     
::Stripe::Webhook.construct_event(
        body
,
        header_signature
,
       
(payload['data']['object']['livemode'] == 'true' ? ENV['STRIPE_LIVE_WEBHOOK_SIGNING_SECRET'] : ENV['STRIPE_TEST_WEBHOOK_SIGNING_SECRET'])
     
)
   
rescue ::Stripe::SignatureVerificationError => e
     
# Error
     
return false
   
end


   
return true
 
end


We keep getting this error: 

Stripe::SignatureVerificationError: No signatures found matching the expected signature for payload

I notice that the body has a lot of newline returns.  Could that be the issue?

Fred Alger

unread,
Jul 11, 2017, 3:55:39 PM7/11/17
to api-d...@lists.stripe.com
Hi Ryan,

Your code looks correct, but there could be a few things going on here and it's hard to say without further debugging. Some thoughts:

1. Your environment variables containing your webhook signing secrets could be blank. For debugging I'd add a check to make sure the signing key is set before calling ::Stripe::Webhook.construct_event()

2. Your framework may not be passing you the raw request body when you call request.body.read(). It's possible that your framework is doing some kind of sanitization or processing on the body before it's passed to your code. You'll need to consult your framework's documentation to determine how to get at the raw body.

If you continue to have problems with this, please write into sup...@stripe.com! We'll be happy to dig in further and update documentation if needed.

Hope that clears things up! Please let me know if you have any further questions, I'm happy to help.

Best,
- Fred.

ry...@paidlabs.com

unread,
Jul 11, 2017, 5:02:37 PM7/11/17
to Stripe API Discussion
Hey Fred—

Thanks for the reply.  So, it seems that it could be related to the `testmode` switch.  I tried using the `STRIPE_LIVE_WEBHOOK_SIGNING_SECRET` for an event that has `livemode: false` and it worked.  Is that expected behavior?  If so, then what are test webhooks for?

Fred Alger

unread,
Jul 11, 2017, 5:13:56 PM7/11/17
to Stripe API Discussion
Hi Ryan,

Glad you were able to trace down the issue!

There is in fact one signing key per webhook. Whether to send live or test mode is set through the Stripe dashboard on each webhook. The signing key is used only to verify that Stripe sent the event data sent to that webhook, and it's managed separately from your test & live mode test and API keys.

The most common approach here is to set up a webhook receiver for testmode events on a staging or development server, and send livemode events to a different webhook URL. Each webhook would have its own signing key, and you'd want to use a single STRIPE_WEBHOOK_SIGNING_SECRET environment variable that you configure set externally in each environment (through application worker configuration, a .env file, or similar).

Based on your code it seems like you're trying to send both live and testmode webhooks to the same URL, which is possible. There's a logic error in your conditional, however, because after unmarshaling the JSON payload['data']['object']['livemode'] will be boolean `true

--
You received this message because you are subscribed to the Google Groups "Stripe API Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss...@lists.stripe.com.
To post to this group, send email to api-d...@lists.stripe.com.
Visit this group at https://groups.google.com/a/lists.stripe.com/group/api-discuss/.

Fred Alger

unread,
Jul 11, 2017, 5:16:17 PM7/11/17
to Stripe API Discussion
Whoops! Hit send too soon.

As I was saying:

There's a logic error in your conditional, however, because after unmarshaling the JSON payload['data']['object']['livemode'] will be boolean `true` instead of the string 'true'. Since you're sending both live and testmode webhooks to the same place, you should store and use both signing keys, and your conditional should be:

(payload['data']['object']['livemode'] ? ENV['STRIPE_LIVE_WEBHOOK_SIGNING_SECRET'] : ENV['STRIPE_TEST_WEBHOOK_SIGNING_SECRET'])

I'd encourage using separate webhooks, but this approach will work as well.

Give me a shout if you need anything else!

Best,
- Fred.

ry...@paidlabs.com

unread,
Jul 12, 2017, 8:36:34 AM7/12/17
to Stripe API Discussion
Got it, and to be clear, I don't think that approach will work.  As I now understand it, we had two webhooks—one dev and one production.   The dev ONLY sends events for livemode=false and the production will send for livemode=false AND livemode=true.

Testing for livemode and using the signing secret based on that still will not work as both webhooks will send livemode=false events, but there is no way to determine which webhook caused it.  

I agree with your original statement that the correct way is to have dev webhooks point to staging or development environment (which shouldn't be connecting live accounts any way).  And then only have a single production webhook pointing to a production consumer, as it will receive both live and test events.

Does that make sense?

jake neal

unread,
Jul 13, 2017, 7:59:14 AM7/13/17
to api-d...@lists.stripe.com
Hey Ryan,

To the best of my knowledge the Live endpoint will only receive webhooks with livemode=true(just tested with my own endpoints, and the results were as expected) -- https://stripe.com/docs/webhooks#configuring-your-webhook-settings

If you're receiving both TEST and LIVE hooks to the same endpoint you should double check your settings(https://dashboard.stripe.com/account/webhooks) to make sure you haven't duplicated your endpoints and if everything looks good there, you should probably take this chat to support instead(https://support.stripe.com/email) so they can look at your account and communicate with you directly.

Regards,

(doesn't work for stripe) Bored late night dev.

To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss+unsubscribe@lists.stripe.com.

Remi J.

unread,
Jul 13, 2017, 8:01:45 AM7/13/17
to api-d...@lists.stripe.com
Hey everyone,

Production Connect webhook endpoints will receive events from both Live mode and Test mode. It's a quirk of those specific types of endpoints due to some legacy reasons with the way Test mode used to work after a Live (production) OAuth connection.

It's something we documented here [1] where we say:

For Connect webhooks, it’s important to note that while only test webhooks will be sent to your development webhook URLs, both live and test webhooks will be sent to your production webhook URLs. This is due to the fact that you can perform both live and test transactions under a production application. For this reason, we recommend you check the livemode value when receiving an event webhook to know what action, if any, should be taken.

Hope this clarifies the situation!
Cheers,
Remi

Reply all
Reply to author
Forward
0 new messages