<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Antoine Braconnier</title>
    <description>The latest articles on Forem by Antoine Braconnier (@antoine_braconnier).</description>
    <link>https://forem.com/antoine_braconnier</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1730308%2Fdfd6da35-7638-4275-92cb-4e21251c79bc.jpg</url>
      <title>Forem: Antoine Braconnier</title>
      <link>https://forem.com/antoine_braconnier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/antoine_braconnier"/>
    <language>en</language>
    <item>
      <title>Secure your Stripe Webhooks and protect yourself from Captain Hook</title>
      <dc:creator>Antoine Braconnier</dc:creator>
      <pubDate>Mon, 29 Jul 2024 10:14:01 +0000</pubDate>
      <link>https://forem.com/wecasa/secure-your-stripe-webhooks-and-protect-yourself-from-captain-hook-4mmo</link>
      <guid>https://forem.com/wecasa/secure-your-stripe-webhooks-and-protect-yourself-from-captain-hook-4mmo</guid>
      <description>&lt;p&gt;If you handle your app payments with Stripe, there's a strong chance that at some point you end up needing using their &lt;strong&gt;webhooks&lt;/strong&gt;. But payments are such a sensitive matter and you certainly don't want to expose your endpoints to every pirate sailing around, now, would you?&lt;/p&gt;

&lt;p&gt;That is why it's really important to setup secured routes without being plundered and loot by some evil corsairs! So grab a grog, and let's find out how to write &lt;strong&gt;a basic Stripe webhook endpoint&lt;/strong&gt;. Following some simple recommendations, it will &lt;strong&gt;safely receive and process events&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhook security
&lt;/h2&gt;

&lt;p&gt;But what is the actual risk of having a webhook endpoint exposed, cast adrift? There's two main issues :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forged requests&lt;/strong&gt; : someone can mimic the same data structure and send forged requests with fake data to your endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2n5dvqd3gg0nfs4ec0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2n5dvqd3gg0nfs4ec0n.png" alt="An image explaining how forged requests work" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replay attack&lt;/strong&gt; : provided that your endpoint is HTTPS and not HTTP, no one but you can read the events sent by Stripe to you. They can, however, capture the encrypted data sent and re-send them again and again, which definitely could cause some harm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe79rrtqodi3ejbr053ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe79rrtqodi3ejbr053ck.png" alt="an image explaining metaphorically how replay attacks work" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily enough, Stripe sends us something really useful in the Headers called &lt;code&gt;STRIPE-SIGNATURE&lt;/code&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"t=1721661059,v1=ef9344dc37fe005b41d7b9d29871cfe1287b2deefbb8d3lk10b9a74b859345734"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this header, we have two values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;t&lt;/code&gt; , a &lt;strong&gt;timestamp&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1&lt;/code&gt; , an &lt;strong&gt;encrypted value&lt;/strong&gt; generated each call that will be used in our app to verify the authenticity of the event sent. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those two values, and the help of the Stripe SDK, we can build a &lt;code&gt;Stripe::Event&lt;/code&gt; object in our app with the payload. This will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assert the data structure&lt;/strong&gt; sent by Stripe;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decrypt the encrypted value&lt;/strong&gt; (&lt;code&gt;v1&lt;/code&gt;), so we know the event is legit;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert that the timestamp is from less than 5 minutes ago&lt;/strong&gt;, to mitigate the replay attack risk. We'll see that this 5 minutes value can be modified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's implement a Stripe webhook!&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up a Stripe webhook endpoint
&lt;/h2&gt;

&lt;p&gt;First, add an endpoint on your Stripe dashboard ( let's call it &lt;code&gt;https://www.myapp.com/payments/events&lt;/code&gt; ) and add an event to listen to. Let's say we want to start shipping goods to the user once we have a successful payment intent. The event we want to listen to is thus &lt;code&gt;payment_intent.succeeded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z7c5hmrzh0gonuy4qij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z7c5hmrzh0gonuy4qij.png" alt="Stripe dashboard " width="800" height="836"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's go to our app and let's code a route with a matching controller. The stripe webhook endpoint is supposed to be a POST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:payments&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ss"&gt;:events&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/payments/events_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Payments&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;EventsController&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, great, that's a good start. We have now a controller that will listen to all of your webhooks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a Stripe::Event object
&lt;/h2&gt;

&lt;p&gt;Now, we actually need to transform the payload into a &lt;code&gt;Stripe::Event&lt;/code&gt; object to make sure that it is safe.&lt;/p&gt;

&lt;p&gt;For the signature decryption, we'll need one last thing : the signing secret of the endpoint.  You can find in the stripe dashboard, in the new webhook endpoint page that you just created. You can add it in your &lt;code&gt;.env&lt;/code&gt; files, under the name &lt;code&gt;PAYMENT_WEBHOOK_SECRET&lt;/code&gt; for example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvbqa1r1uuxpqn9uxvtc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvbqa1r1uuxpqn9uxvtc.png" alt="Showing where the secret key is in the endpoint page" width="610" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we have everything necessary to create a service to build this event. This service will accept as a parameter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;payload&lt;/strong&gt;, which will be a string;&lt;/li&gt;
&lt;li&gt;and the &lt;strong&gt;signature header&lt;/strong&gt; (with the &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;t&lt;/code&gt; values, remember?) for the security check.
We'll also need to use the &lt;strong&gt;stripe webhook secret&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The building of the event can raise two kind of errors in case of bad data, and it's important we handle these errors properly. So let's add a basic error handler with this in mind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/payments/build_event_service.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Payments&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BuildEventService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationService&lt;/span&gt;
    &lt;span class="no"&gt;WEBHOOK_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PAYMENT_WEBHOOK_SECRET'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="vi"&gt;@payment_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;construct_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# :tolerance argument will change the 5 minutes limit to one minute&lt;/span&gt;
        &lt;span class="ss"&gt;tolerance: &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParserError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SignatureVerificationError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;WebhookSecurityCheckError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have a service that will effectively build and return a &lt;code&gt;Stripe::Event&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Let's add it in our controller! And let's take the opportunity to think about what the controller needs to respond here. Stripe will expect a &lt;code&gt;200&lt;/code&gt; response from its webhook call, so let's return that, and let's maybe return a &lt;code&gt;401&lt;/code&gt; if our security check didn't pass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/payments/events_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Payments&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;EventsController&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
      &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BuildEventService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;signature_header: &lt;/span&gt;&lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# do stuff with the event created&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;WebhookSecurityCheckError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
       &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;payload&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signature_header&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_STRIPE_SIGNATURE'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok! We have now an endpoint that is all setup for a basic security check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dispatching Stripe events
&lt;/h2&gt;

&lt;p&gt;However, this is still not entirely ideal. Stripe expects to receive an answer quickly. In fact, this controller should do the most minimal thing possible :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;check&lt;/strong&gt; that the event is indeed sent by Stripe,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;delegate ASAP the responsability&lt;/strong&gt; and the payload to a corresponding job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let's quickly add a &lt;strong&gt;dispatcher&lt;/strong&gt; that will do just that. For this, we'll need to have a hash with the corresponding event types mapping to jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/payments/dispatch_event_service.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Payments&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DispatchEventService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationService&lt;/span&gt;
    &lt;span class="no"&gt;EVENT_TYPE_TO_JOB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s1"&gt;'payment_intent.succeeded'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SendProductToClientJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="no"&gt;EVENT_TYPE_TO_JOB&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use this dispatcher in our controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/payments/events_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Payments&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;EventsController&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
      &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BuildEventService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;signature_header: &lt;/span&gt;&lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;DispatchEventService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;WebhookSecurityCheckError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
       &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# [...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;For testing, you'll probably need to &lt;strong&gt;stub&lt;/strong&gt; your method call &lt;code&gt;construct_event&lt;/code&gt; to make it return what you need. And to test manually and locally the webhook, a good and easy way to do it without having to use a tool like ngrok is to use the &lt;strong&gt;Stripe CLI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the command to install Stripe CLI via Homebrew :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;stripe/stripe-cli/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then login with your credentials :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stripe login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, you can easily forward any events necessary to your local server :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stripe listen &lt;span class="nt"&gt;--events&lt;/span&gt; payment_intent.succeeded &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--forward-to&lt;/span&gt; localhost:3000/payments/events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to trigger the webhook when necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stripe trigger payment_intent.succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Of course, you can go further in securing your webhook. For example, you can use the &lt;strong&gt;idempotency key&lt;/strong&gt; that each event provide to make sure that the event is unique.&lt;/p&gt;

&lt;p&gt;With this basic setup, you can already receive pretty safely Stripe webhooks, and you can add events in the &lt;code&gt;EVENT_TYPE_TO_JOB&lt;/code&gt; whenever you need to. Your code will safely build a &lt;code&gt;Stripe::Event&lt;/code&gt; object and  check the correct event type to delegate the attributes of the payload to a matching job.&lt;/p&gt;

&lt;p&gt;Thank you for reading. I invite you to subscribe so you don't miss the next articles that will be released.&lt;/p&gt;

&lt;p&gt;Happy sailing (and coding)! 🦜 🏴‍☠️&lt;/p&gt;

</description>
      <category>rails</category>
      <category>stripe</category>
      <category>ruby</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
